diff --git a/.gitignore b/.gitignore index c031eb8..8e41f40 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ erl_crash.dump /doc *.ez +/cover diff --git a/CHANGELOG.md b/CHANGELOG.md index c8d8884..f92fa84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,98 +1,109 @@ # Changelog +## Unreleased + +### Backward Incompatible Changes + +- There is no longer a default serializer for `application/json`. Please make + sure to register a serializer with `OAuth2.Client.put_serializer/3`. +- Serializers are now registered via `OAuth2.Client.put_serializer/3`. + This change allows applications wrapping `oauth2` a way to provide default + serializers without requiring the user to manually configure a serializer. + ## v0.9.4 (2018-10-18) ### Improvements -* Relaxed `hackney` version requirements +- Relaxed `hackney` version requirements ## v0.9.3 (2018-08-13) ### Bug fixes -* Various type specs fixed +- Various type specs fixed ## v0.9.2 (2017-11-17) ### Bug fixes -* Updates the `OAuth2.Client.get_token!` function to handle error `OAuth2.Response` structs. +- Updates the `OAuth2.Client.get_token!` function to handle error `OAuth2.Response` structs. ## v0.9.1 (2017-03-10) ### Improvements -* Fix dialyzer warnings. -* Update `hackney` to `1.7` +- Fix dialyzer warnings. +- Update `hackney` to `1.7` ### Bug fixes -* De-dupe headers. +- De-dupe headers. ## v0.9.0 (2017-02-02) ### Improvements -* Remove deprecated usage of `Behaviour` and `defcallback` -* Provides better support for configuring `request_opts` that will be used on +- Remove deprecated usage of `Behaviour` and `defcallback` +- Provides better support for configuring `request_opts` that will be used on every request. This is useful for configuring SSL options, etc. -* Provides support for `hackney`s streaming of responses. -* Better warnings when a serializer isn't properly configured. +- Provides support for `hackney`s streaming of responses. +- Better warnings when a serializer isn't properly configured. ### Backward Incompatible Changes -* Responses with status codes between `400..599` will now return `{:error, - %OAuth2.Response{}}` instead of `{:ok, %OAuth2.Response{}}` -* When using the `!` versions of functions, `{:error, %OAuth2.Response{}}` will +- Responses with status codes between `400..599` will now return `{:error, %OAuth2.Response{}}` instead of `{:ok, %OAuth2.Response{}}` +- When using the `!` versions of functions, `{:error, %OAuth2.Response{}}` will be converted to an `%OAuth2.Error{}` and raised. ## v0.8.3 (2017-01-26) -* Fix compile-time warnings for Elixir 1.4 -* Fix dialyzer warnings on `@type params` -* Fix `content-type` resolving when there are multiple params -* Return the same refresh token unless a new one is provided -* Raise an exception when missing serializer configuration +- Fix compile-time warnings for Elixir 1.4 +- Fix dialyzer warnings on `@type params` +- Fix `content-type` resolving when there are multiple params +- Return the same refresh token unless a new one is provided +- Raise an exception when missing serializer configuration ## v0.8.2 (2016-11-22) ### Bug Fixes -* Fixed an issue in handling non-standard `expires` key in access token +- Fixed an issue in handling non-standard `expires` key in access token requests. ## v0.8.1 (2016-11-18) ### Improvements -* Added the ability to debug responses from the provider. +- Added the ability to debug responses from the provider. ### Bug Fixes -* Fixed regression in handling `text/plain` content-type for tokens in #74 +- Fixed regression in handling `text/plain` content-type for tokens in #74 ## v0.8.0 (2016-10-05) ### Improvements -* Added `OAuth2.Client.basic_auth/1` convenience function. +- Added `OAuth2.Client.basic_auth/1` convenience function. ### Bug Fixes -* Fixed broken `RefreshToken` strategy reported in #66 -* Fixed an issue where checking the `content-type` was defaulting to +- Fixed broken `RefreshToken` strategy reported in #66 +- Fixed an issue where checking the `content-type` was defaulting to `application/json` causing Poison to explode. ## v0.7.0 (2016-08-16) ### Improvements -* Add support for custom serializers based on MIME types. -* Remove dependency on `HTTPoison` in favor of using `hackney` directly. -* Remove dependency on `mimetype_parser`. -* `Poison` is now only a `test` dependency. + +- Add support for custom serializers based on MIME types. +- Remove dependency on `HTTPoison` in favor of using `hackney` directly. +- Remove dependency on `mimetype_parser`. +- `Poison` is now only a `test` dependency. ### Bug Fixes -* `expires_in` values that are returned as strings are now properly parsed into integers for `expires_at`. + +- `expires_in` values that are returned as strings are now properly parsed into integers for `expires_at`. ### Backward Incompatible Changes @@ -109,34 +120,36 @@ Please consult the [README](https://github.com/scrogson/oauth2/blob/v0.7.0/READM The following methods have been moved and adjusted so that they take a `OAuth2.Client.t` which contains a token, rather than a token directly: -* `OAuth2.AccessToken.get` -> `OAuth2.Client.get` -* `OAuth2.AccessToken.get!` -> `OAuth2.Client.get!` -* `OAuth2.AccessToken.put` -> `OAuth2.Client.put` -* `OAuth2.AccessToken.put!` -> `OAuth2.Client.put!` -* `OAuth2.AccessToken.patch` -> `OAuth2.Client.patch` -* `OAuth2.AccessToken.patch!` -> `OAuth2.Client.patch!` -* `OAuth2.AccessToken.post` -> `OAuth2.Client.post` -* `OAuth2.AccessToken.post!` -> `OAuth2.Client.post!` -* `OAuth2.AccessToken.delete` -> `OAuth2.Client.delete` -* `OAuth2.AccessToken.delete!` -> `OAuth2.Client.delete!` -* `OAuth2.AccessToken.refresh` -> `OAuth2.Client.refresh_token` -* `OAuth2.AccessToken.refresh!` -> `OAuth2.Client.refresh_token!` +- `OAuth2.AccessToken.get` -> `OAuth2.Client.get` +- `OAuth2.AccessToken.get!` -> `OAuth2.Client.get!` +- `OAuth2.AccessToken.put` -> `OAuth2.Client.put` +- `OAuth2.AccessToken.put!` -> `OAuth2.Client.put!` +- `OAuth2.AccessToken.patch` -> `OAuth2.Client.patch` +- `OAuth2.AccessToken.patch!` -> `OAuth2.Client.patch!` +- `OAuth2.AccessToken.post` -> `OAuth2.Client.post` +- `OAuth2.AccessToken.post!` -> `OAuth2.Client.post!` +- `OAuth2.AccessToken.delete` -> `OAuth2.Client.delete` +- `OAuth2.AccessToken.delete!` -> `OAuth2.Client.delete!` +- `OAuth2.AccessToken.refresh` -> `OAuth2.Client.refresh_token` +- `OAuth2.AccessToken.refresh!` -> `OAuth2.Client.refresh_token!` Additionally, the following methods have been moved to `OAuth2.Request` -* `OAuth2.AccessToken.request` -> `OAuth2.Request.request` -* `OAuth2.AccessToken.request!` -> `OAuth2.Request.request!` +- `OAuth2.AccessToken.request` -> `OAuth2.Request.request` +- `OAuth2.AccessToken.request!` -> `OAuth2.Request.request!` Diff: https://github.com/scrogson/oauth2/compare/v0.6.0...v0.7.0 ## v0.6.0 (2016-06-24) ### Improvements -* Use Poison ~> 2.0 -* Reset client headers after fetching the token + +- Use Poison ~> 2.0 +- Reset client headers after fetching the token ### Bug Fixes -* Fix up auth code flow to match the RFC + +- Fix up auth code flow to match the RFC Diff: https://github.com/scrogson/oauth2/compare/v0.5.0...v0.6.0 @@ -144,21 +157,21 @@ Diff: https://github.com/scrogson/oauth2/compare/v0.5.0...v0.6.0 ### Improvements -* You can now request a refresh token with `OAuth2.AccessToken.refresh`. The `!` alternative is also available. -* Added `Bypass` for improved testability. -* `Plug` is no longer a direct dependency. It is only included as a test dependency through the `Bypass` library. -* `OAuth2.AccessToken` now supports `DELETE` requests with `delete` and `delete!` -* More tests! +- You can now request a refresh token with `OAuth2.AccessToken.refresh`. The `!` alternative is also available. +- Added `Bypass` for improved testability. +- `Plug` is no longer a direct dependency. It is only included as a test dependency through the `Bypass` library. +- `OAuth2.AccessToken` now supports `DELETE` requests with `delete` and `delete!` +- More tests! ### Bug Fixes -* Params are no longer sent in both the body and as a query string for `POST` requests with `OAuth2.Client.get_token` -* Responses will no longer be parsed automatically if the `content-type` is not supported by this lib. Registering custom parsers is a future goal for this library. -* Errors are now properly raised when they occur. +- Params are no longer sent in both the body and as a query string for `POST` requests with `OAuth2.Client.get_token` +- Responses will no longer be parsed automatically if the `content-type` is not supported by this lib. Registering custom parsers is a future goal for this library. +- Errors are now properly raised when they occur. ### Backwards Incompatible Changes -* `OAuth2.new/1` has been removed. Use `OAuth2.Client.new/1` instead. +- `OAuth2.new/1` has been removed. Use `OAuth2.Client.new/1` instead. Diff: https://github.com/scrogson/oauth2/compare/v0.4.0...v0.5.0 @@ -166,17 +179,17 @@ Diff: https://github.com/scrogson/oauth2/compare/v0.4.0...v0.5.0 ### Additions/Improvements -* `OAuth2.AccessToken` now supports: `post`, `post!`, `put`, `put!`, `patch`, and `patch!`. -* Better documentation -* Test coverage improved +- `OAuth2.AccessToken` now supports: `post`, `post!`, `put`, `put!`, `patch`, and `patch!`. +- Better documentation +- Test coverage improved ### Bug fixes -* Empty response bodies are no longer decoded +- Empty response bodies are no longer decoded ### Breaking changes -* `OAuth2.AccessToken.get!/4` now returns `OAuth2.Response{}` instead of just the parsed body. +- `OAuth2.AccessToken.get!/4` now returns `OAuth2.Response{}` instead of just the parsed body. ### Aknowledgements @@ -190,7 +203,6 @@ Bump `Plug` dependency to `1.0`. Diff: https://github.com/scrogson/oauth2/compare/v0.2.0...v0.3.0 - ## v0.2.0 (2015-07-13) - `:erlang.now` was replaced with `:os.timestamp` for compatibility with Erlang 18 @@ -198,7 +210,6 @@ Diff: https://github.com/scrogson/oauth2/compare/v0.2.0...v0.3.0 Diff: https://github.com/scrogson/oauth2/compare/v0.1.1...v0.2.0 - ## v0.1.1 (2015-04-18) - Remove compilation warnings. @@ -206,7 +217,6 @@ Diff: https://github.com/scrogson/oauth2/compare/v0.1.1...v0.2.0 Diff: https://github.com/scrogson/oauth2/compare/v0.1.0...v0.1.1 - ## v0.1.0 (2015-04-14) This release bring breaking changes and more documentation. @@ -215,7 +225,6 @@ Please see the [README](https://github.com/scrogson/oauth2/blob/v0.1.0/README.md Diff: https://github.com/scrogson/oauth2/compare/v0.0.5...v0.1.0 - ## v0.0.5 (2015-04-11) - Handles Facebooks `expires` key for Access Tokens. @@ -223,7 +232,6 @@ Diff: https://github.com/scrogson/oauth2/compare/v0.0.5...v0.1.0 Diff: https://github.com/scrogson/oauth2/compare/0.0.3...v0.0.5 - ## v0.0.3 (2015-01-12) - Relax version requirements for Poison. @@ -240,4 +248,3 @@ http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.4 Initial release. This initial release includes a functional authorization code strategy: http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.1 - diff --git a/README.md b/README.md index 8799623..9ceb91c 100644 --- a/README.md +++ b/README.md @@ -25,35 +25,27 @@ end ## Configure a serializer This library can be configured to handle encoding and decoding requests and -responses automatically. +responses automatically based on the `accept` and/or `content-type` headers. -If you're using [Poison](https://hex.pm/packages/poison) for JSON in your -application, this library is already pre-configured to use it for `"application/json"` -request and response bodies. You will still need to include it as a dependency though. - -If you need to handle different MIME types, you can simply configure it like so: +If you need to handle various MIME types, you can simply register serializers like so: ```elixir -# config/config.exs -config :oauth2, - serializers: %{ - "application/vnd.api+json" => Poison, - "application/xml" => MyApp.XmlParser, - } +OAuth2.Client.put_serializer(client, "application/vnd.api+json", Jason) +OAuth2.Client.put_serializer(client, "application/xml", MyApp.Parsers.XML) ``` -The `serializers` option is a map where the keys are MIME types and the values -are modules. - The modules are expected to export `encode!/1` and `decode!/1`. ```elixir -defmodule MyApp.XmlParser do +defmodule MyApp.Parsers.XML do def encode!(data), do: # ... def decode!(binary), do: # ... end ``` +Please see the documentation for [OAuth2.Serializer](https://hexdocs.pm/oauth2/OAuth2.Serializer.html) +for more details. + ## Debug mode Some times its handy to see what's coming back from the response when getting @@ -158,6 +150,7 @@ defmodule GitHub do authorize_url: "https://github.com/login/oauth/authorize", token_url: "https://github.com/login/oauth/access_token" ]) + |> OAuth2.Client.put_serializer("application/json", Jason) end def authorize_url! do diff --git a/config/config.exs b/config/config.exs index 473fc5c..a2d3221 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,10 +1,9 @@ use Mix.Config -config :logger, level: :info +config :logger, level: :debug 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}, request_opts: [] diff --git a/coveralls.json b/coveralls.json new file mode 100644 index 0000000..20ac360 --- /dev/null +++ b/coveralls.json @@ -0,0 +1,6 @@ +{ + "skip_files": [ + "lib/oauth2/strategy.ex", + "test/support/test_helpers.ex" + ] +} diff --git a/lib/oauth2/client.ex b/lib/oauth2/client.ex index b234049..87e2ba5 100644 --- a/lib/oauth2/client.ex +++ b/lib/oauth2/client.ex @@ -41,6 +41,7 @@ defmodule OAuth2.Client do @type redirect_uri :: binary @type ref :: reference | nil @type request_opts :: Keyword.t + @type serializers :: %{binary => module} @type site :: binary @type strategy :: module @type token :: AccessToken.t | nil @@ -56,6 +57,7 @@ defmodule OAuth2.Client do redirect_uri: redirect_uri, ref: ref, request_opts: request_opts, + serializers: serializers, site: site, strategy: strategy, token: token, @@ -71,6 +73,7 @@ defmodule OAuth2.Client do redirect_uri: "", ref: nil, request_opts: [], + serializers: %{}, site: "", strategy: OAuth2.Strategy.AuthCode, token: nil, @@ -179,7 +182,9 @@ defmodule OAuth2.Client do @spec put_headers(t, list) :: t def put_headers(%Client{} = client, []), do: client def put_headers(%Client{} = client, [{k,v}|rest]) do - client |> put_header(k,v) |> put_headers(rest) + client + |> put_header(k, v) + |> put_headers(rest) end @doc false @@ -202,6 +207,43 @@ defmodule OAuth2.Client do url end + @doc """ + Register a serialization module for a given mime type. + + ## Example + + iex> client = OAuth2.Client.put_serializer(%OAuth2.Client{}, "application/json", Jason) + %OAuth2.Client{serializers: %{"application/json" => Jason}} + iex> OAuth2.Client.get_serializer(client, "application/json") + Jason + """ + @spec put_serializer(t, binary, atom) :: t + def put_serializer(%Client{serializers: serializers} = client, mime, module) + when is_binary(mime) and is_atom(module) do + %Client{client | serializers: Map.put(serializers, mime, module)} + end + + @doc """ + Un-register a serialization module for a given mime type. + + ## Example + + iex> client = OAuth2.Client.delete_serializer(%OAuth2.Client{}, "application/json") + %OAuth2.Client{} + iex> OAuth2.Client.get_serializer(client, "application/json") + nil + """ + @spec delete_serializer(t, binary) :: t + def delete_serializer(%Client{serializers: serializers} = client, mime) do + %Client{client | serializers: Map.delete(serializers, mime)} + end + + @doc false + @spec get_serializer(t, binary) :: atom + def get_serializer(%Client{serializers: serializers}, mime) do + Map.get(serializers, mime) + end + @doc """ Fetches an `OAuth2.AccessToken` struct by making a request to the token endpoint. diff --git a/lib/oauth2/request.ex b/lib/oauth2/request.ex index 9933bea..0dfb40c 100644 --- a/lib/oauth2/request.ex +++ b/lib/oauth2/request.ex @@ -1,10 +1,10 @@ defmodule OAuth2.Request do @moduledoc false - + require Logger import OAuth2.Util - alias OAuth2.{Client, Error, Response, Serializer} + alias OAuth2.{Client, Error, Response} @type body :: any @@ -17,13 +17,14 @@ defmodule OAuth2.Request do url = client |> process_url(url) |> process_params(opts[:params]) headers = req_headers(client, headers) |> Enum.uniq content_type = content_type(headers) - body = encode_request_body(body, content_type) + serializer = Client.get_serializer(client, content_type) + body = encode_request_body(body, content_type, serializer) headers = process_request_headers(headers, content_type) req_opts = Keyword.merge(client.request_opts, opts) if Application.get_env(:oauth2, :debug) do Logger.debug(""" - OAuth2 Provider Request + OAuth2 Provider Request url: #{inspect url} method: #{inspect method} headers: #{inspect headers} @@ -36,9 +37,9 @@ defmodule OAuth2.Request do {:ok, ref} when is_reference(ref) -> {:ok, ref} {:ok, status, headers, ref} when is_reference(ref) -> - process_body(status, headers, ref) + process_body(client, status, headers, ref) {:ok, status, headers, body} when is_binary(body) -> - process_body(status, headers, body) + process_body(client, status, headers, body) {:error, reason} -> {:error, %Error{reason: reason}} end @@ -80,16 +81,16 @@ defmodule OAuth2.Request do end end - defp process_body(status, headers, ref) when is_reference(ref) do + defp process_body(client, status, headers, ref) when is_reference(ref) do case :hackney.body(ref) do {:ok, body} -> - process_body(status, headers, body) + process_body(client, 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) + defp process_body(client, status, headers, body) when is_binary(body) do + resp = Response.new(client, status, headers, body) case status do status when status in 200..399 -> {:ok, resp} @@ -120,9 +121,14 @@ defmodule OAuth2.Request do end end - defp encode_request_body("", _), do: "" - defp encode_request_body([], _), do: "" - defp encode_request_body(body, "application/x-www-form-urlencoded"), + defp encode_request_body("", _, _), do: "" + defp encode_request_body([], _, _), do: "" + defp encode_request_body(body, "application/x-www-form-urlencoded", _), do: URI.encode_query(body) - defp encode_request_body(body, type), do: Serializer.encode!(body, type) + defp encode_request_body(body, _mime, nil) do + body + end + defp encode_request_body(body, _mime, serializer) do + serializer.encode!(body) + end end diff --git a/lib/oauth2/response.ex b/lib/oauth2/response.ex index 0353338..0b6e958 100644 --- a/lib/oauth2/response.ex +++ b/lib/oauth2/response.ex @@ -12,7 +12,7 @@ defmodule OAuth2.Response do require Logger import OAuth2.Util - alias OAuth2.Serializer + alias OAuth2.Client @type status_code :: integer @type headers :: list @@ -27,9 +27,11 @@ defmodule OAuth2.Response do defstruct status_code: nil, headers: [], body: nil @doc false - def new(code, headers, body) do + def new(client, code, headers, body) do headers = process_headers(headers) - body = decode_response_body(body, content_type(headers)) + content_type = content_type(headers) + serializer = Client.get_serializer(client, content_type) + body = decode_response_body(body, content_type, serializer) resp = %__MODULE__{status_code: code, headers: headers, body: body} if Application.get_env(:oauth2, :debug) do @@ -43,16 +45,22 @@ defmodule OAuth2.Response do Enum.map(headers, fn {k, v} -> {String.downcase(k), v} end) end - defp decode_response_body("", _type), do: "" - defp decode_response_body(" ", _type), do: "" + defp decode_response_body("", _type, _), do: "" + defp decode_response_body(" ", _type, _), do: "" # Facebook sends text/plain tokens!? - defp decode_response_body(body, "text/plain") do + defp decode_response_body(body, "text/plain", _) do case URI.decode_query(body) do %{"access_token" => _} = token -> token _ -> body end end - defp decode_response_body(body, "application/x-www-form-urlencoded"), - do: URI.decode_query(body) - defp decode_response_body(body, type), do: Serializer.decode!(body, type) + defp decode_response_body(body, "application/x-www-form-urlencoded", _) do + URI.decode_query(body) + end + defp decode_response_body(body, _mime, nil) do + body + end + defp decode_response_body(body, _type, serializer) do + serializer.decode!(body) + end end diff --git a/lib/oauth2/serializer.ex b/lib/oauth2/serializer.ex index 826cb7e..7fffcd8 100644 --- a/lib/oauth2/serializer.ex +++ b/lib/oauth2/serializer.ex @@ -1,50 +1,15 @@ defmodule OAuth2.Serializer do - @moduledoc false + @moduledoc """ + A serializer is responsible for encoding/decoding request/response bodies. - require Logger + ## Example - defmodule NullSerializer do - @moduledoc false + defmodule MyApp.JSON do + def encode!(data), do: Jason.encode!(data) + def decode!(binary), do: Jason.decode!(binary) + end + """ - @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 - 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 + @callback encode!(map) :: binary + @callback decode!(binary) :: map end diff --git a/mix.exs b/mix.exs index dcbc179..74935cd 100644 --- a/mix.exs +++ b/mix.exs @@ -22,17 +22,17 @@ defmodule OAuth2.Mixfile do end def application do - [applications: [:logger, :hackney], - env: [serializers: %{"application/json" => Poison}]] + [applications: [:logger, :hackney]] end defp deps do - [{:hackney, "~> 1.7"}, + [{:hackney, "~> 1.13.0"}, # Test dependencies - {:poison, "~> 3.0", only: :test}, - {:bypass, "~> 0.6", only: :test}, - {:excoveralls, "~> 0.5", only: :test}, + {:jason, "~> 1.0", only: :test}, + {:bypass, "~> 0.9", only: :test}, + {:plug_cowboy, "~> 1.0", only: :test}, + {:excoveralls, "~> 0.9", only: :test}, {:dialyxir, "~> 0.5", only: [:dev], runtime: false}, # Docs dependencies diff --git a/mix.lock b/mix.lock index 770be31..61fa37a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,24 +1,26 @@ %{ - "bypass": {:hex, :bypass, "0.6.0", "fd0a8004fada4464e2ba98497755310b892a097f2fd975f4f787cf264066a335", [:mix], [{:cowboy, "~> 1.0", [repo: "hexpm", hex: :cowboy, optional: false]}, {:plug, "~> 1.0", [repo: "hexpm", hex: :plug, optional: false]}], "hexpm"}, - "certifi": {:hex, :certifi, "1.0.0", "1c787a85b1855ba354f0b8920392c19aa1d06b0ee1362f9141279620a5be2039", [:rebar3], [], "hexpm"}, - "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, optional: false]}]}, - "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []}, - "dialyxir": {:hex, :dialyxir, "0.5.0", "5bc543f9c28ecd51b99cc1a685a3c2a1a93216990347f259406a910cf048d1d7", [:mix], []}, + "bypass": {:hex, :bypass, "0.9.0", "4cedcd326eeec497e0090a73d351cbd0f11e39329ddf9095931b03da9b6dc417", [:mix], [{:cowboy, "~> 1.0 or ~> 2.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, + "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, + "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "excoveralls": {:hex, :excoveralls, "0.6.1", "9e946b6db84dba592f47632157ecd135a46384b98a430fd16007dc910c70348b", [:mix], [{:exjsx, "~> 3.0", [repo: "hexpm", hex: :exjsx, optional: false]}, {:hackney, ">= 0.12.0", [repo: "hexpm", hex: :hackney, optional: false]}], "hexpm"}, - "exjsx": {:hex, :exjsx, "3.2.1", "1bc5bf1e4fd249104178f0885030bcd75a4526f4d2a1e976f4b428d347614f0f", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, optional: false]}]}, - "hackney": {:hex, :hackney, "1.7.1", "e238c52c5df3c3b16ce613d3a51c7220a784d734879b1e231c9babd433ac1cb4", [:rebar3], [{:certifi, "1.0.0", [repo: "hexpm", hex: :certifi, optional: false]}, {:idna, "4.0.0", [repo: "hexpm", hex: :idna, optional: false]}, {:metrics, "1.0.1", [repo: "hexpm", hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [repo: "hexpm", hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [repo: "hexpm", hex: :ssl_verify_fun, optional: false]}], "hexpm"}, - "idna": {:hex, :idna, "4.0.0", "10aaa9f79d0b12cf0def53038547855b91144f1bfcc0ec73494f38bb7b9c4961", [:rebar3], [], "hexpm"}, - "jsx": {:hex, :jsx, "2.8.1", "1453b4eb3615acb3e2cd0a105d27e6761e2ed2e501ac0b390f5bbec497669846", [:mix, :rebar3], []}, + "excoveralls": {:hex, :excoveralls, "0.10.1", "407d50ac8fc63dfee9175ccb4548e6c5512b5052afa63eedb9cd452a32a91495", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, - "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], [], "hexpm"}, - "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, + "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, - "plug": {:hex, :plug, "1.3.3", "d9be189924379b4e9d470caef87380d09549aea1ceafe6a0d41292c8c317c923", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [repo: "hexpm", hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [repo: "hexpm", hex: :mime, optional: false]}], "hexpm"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []}, - "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], []}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []}, + "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, + "plug": {:hex, :plug, "1.7.0", "cd8c8de89bd9de55eba1c918bf0e7f319737e109b6014875104af025a623e16e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"}, + "plug_cowboy": {:hex, :plug_cowboy, "1.0.0", "2e2a7d3409746d335f451218b8bb0858301c3de6d668c3052716c909936eb57a", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, + "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, } diff --git a/test/oauth2/access_token_test.exs b/test/oauth2/access_token_test.exs index 2dcd82b..b786ba0 100644 --- a/test/oauth2/access_token_test.exs +++ b/test/oauth2/access_token_test.exs @@ -4,7 +4,7 @@ defmodule OAuth2.AccessTokenTest do import OAuth2.TestHelpers, only: [unix_now: 0] - alias OAuth2.{AccessToken, Response} + alias OAuth2.{AccessToken, Client, Response} test "new from binary token" do token = AccessToken.new("abc123") @@ -12,7 +12,7 @@ defmodule OAuth2.AccessTokenTest do end test "new with 'expires_in' param" do - response = Response.new(200, [{"content-type", "application/x-www-form-urlencoded"}], "access_token=abc123&expires_in=123") + response = Response.new(%Client{}, 200, [{"content-type", "application/x-www-form-urlencoded"}], "access_token=abc123&expires_in=123") token = AccessToken.new(response.body) assert token.access_token == "abc123" assert token.expires_at == 123 + unix_now() @@ -21,7 +21,7 @@ defmodule OAuth2.AccessTokenTest do end test "new with 'expires' param" do - response = Response.new(200, [{"content-type", "application/x-www-form-urlencoded"}], "access_token=abc123&expires=123") + response = Response.new(%Client{}, 200, [{"content-type", "application/x-www-form-urlencoded"}], "access_token=abc123&expires=123") token = AccessToken.new(response.body) assert token.access_token == "abc123" assert token.expires_at == 123 + unix_now() @@ -30,7 +30,7 @@ defmodule OAuth2.AccessTokenTest do end test "new from text/plain content-type" do - response = Response.new(200, [{"content-type", "text/plain"}], "access_token=abc123&expires=123") + response = Response.new(%Client{}, 200, [{"content-type", "text/plain"}], "access_token=abc123&expires=123") token = AccessToken.new(response.body) assert token.access_token == "abc123" assert token.expires_at == 123 + unix_now() diff --git a/test/oauth2/error_test.exs b/test/oauth2/error_test.exs new file mode 100644 index 0000000..063a3a7 --- /dev/null +++ b/test/oauth2/error_test.exs @@ -0,0 +1,11 @@ +defmodule OAuth2.ErrorTest do + use ExUnit.Case, async: false + + alias OAuth2.Error + + test "message" do + assert Error.message(%Error{reason: :econnrefused}) == "Connection refused" + assert Error.message(%Error{reason: "blah"}) == "blah" + assert Error.message(%Error{reason: :blah}) == ":blah" + end +end diff --git a/test/oauth2/response_test.exs b/test/oauth2/response_test.exs new file mode 100644 index 0000000..2fae594 --- /dev/null +++ b/test/oauth2/response_test.exs @@ -0,0 +1,22 @@ +defmodule OAuth2.ResponseTest do + use ExUnit.Case, async: false + + alias OAuth2.Response + + import ExUnit.CaptureLog + + test "debug response body" do + Application.put_env(:oauth2, :debug, true) + + output = capture_log(fn -> Response.new(%OAuth2.Client{}, 200, [{"content-type", "text/plain"}], "hello") end) + assert output =~ ~s(OAuth2 Provider Response) + assert output =~ ~s(body: "hello") + + Application.put_env(:oauth2, :debug, false) + end + + test "text/plain body passes through body" do + response = Response.new(%OAuth2.Client{}, 200, [{"content-type", "text/plain"}], "hello") + assert response.body == "hello" + end +end diff --git a/test/oauth2_test.exs b/test/oauth2_test.exs index 64a3938..99f1188 100644 --- a/test/oauth2_test.exs +++ b/test/oauth2_test.exs @@ -2,6 +2,8 @@ defmodule OAuth2Test do use ExUnit.Case import OAuth2.TestHelpers + doctest OAuth2 + @client build_client(client_id: "abc123", client_secret: "xyz987", site: "https://api.github.com", diff --git a/test/serializer_test.exs b/test/serializer_test.exs deleted file mode 100644 index 9462258..0000000 --- a/test/serializer_test.exs +++ /dev/null @@ -1,44 +0,0 @@ -defmodule OAuth2.SerializerTest do - use ExUnit.Case - alias OAuth2.Serializer - - defmodule TestSerializer do - def decode!(_), do: "decode_ok" - def encode!(_), do: "encode_ok" - end - @json_mime "application/json" - - def with_serializers(serializers, fun) do - backup = Application.get_env(:oauth2, :serializers) - try do - Application.put_env(:oauth2, :serializers, serializers) - fun.() - after - Application.put_env(:oauth2, :serializers, backup) - end - end - - test "has default json serializer" do - decoded = Serializer.decode!("{\"foo\": 1}", @json_mime) - assert decoded == %{"foo" => 1} - end - - test "accepts serializer override" do - with_serializers(%{@json_mime => TestSerializer}, fn -> - decoded = Serializer.decode!("{\"foo\": 1}", @json_mime) - assert decoded == "decode_ok" - - encoded = Serializer.encode!(%{"foo" => 1}, @json_mime) - assert encoded == "encode_ok" - end) - end - - test "raise error when serializers are misconfigured" do - with_serializers(nil, fn -> - assert_raise(RuntimeError, ~r/configuration/i, fn -> - Serializer.decode!("{\"foo\": 1}", @json_mime) - end) - end) - end -end - diff --git a/test/support/test_helpers.ex b/test/support/test_helpers.ex index 4dd71e6..4a04798 100644 --- a/test/support/test_helpers.ex +++ b/test/support/test_helpers.ex @@ -34,7 +34,7 @@ defmodule OAuth2.TestHelpers do defp parse_req_body(conn) do opts = [parsers: [:urlencoded, :json], pass: ["*/*"], - json_decoder: Poison] + json_decoder: Jason] Plug.Parsers.call(conn, Plug.Parsers.init(opts)) end @@ -55,13 +55,14 @@ defmodule OAuth2.TestHelpers do def json(conn, status, body \\ []) do conn |> put_resp_header("content-type", "application/json") - |> send_resp(status, Poison.encode!(body)) + |> send_resp(status, Jason.encode!(body)) end def build_client(opts \\ []) do default_client_opts() |> Keyword.merge(opts) |> OAuth2.Client.new() + |> OAuth2.Client.put_serializer("application/json", Jason) end def tokenize_client(opts \\ [], %OAuth2.Client{} = client) do diff --git a/test/test_helper.exs b/test/test_helper.exs index 8c2aa6f..98fd96f 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,2 +1,3 @@ Application.ensure_all_started(:bypass) -ExUnit.start +Application.put_env(:oauth2, :warn_missing_serializer, false) +ExUnit.start()