diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..6730f56 --- /dev/null +++ b/.credo.exs @@ -0,0 +1,162 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any exec using `mix credo -C `. If no exec name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: ["lib/", "src/", "test/", "web/", "apps/"], + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: true, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, + [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + {Credo.Check.Design.TagFIXME, []}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + # TODO: enable by default in Credo 1.1 + {Credo.Check.Readability.UnnecessaryAliasExpansion, false}, + {Credo.Check.Readability.VariableNames, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MapInto, false}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + + # + ## Warnings + # + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.LazyLogging, false}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + + # + # Controversial and experimental checks (opt-in, just replace `false` with `[]`) + # + {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, + {Credo.Check.Design.DuplicatedCode, false}, + {Credo.Check.Readability.MultiAlias, false}, + {Credo.Check.Readability.Specs, false}, + {Credo.Check.Readability.SinglePipe, false}, + {Credo.Check.Refactor.ABCSize, false}, + {Credo.Check.Refactor.AppendSingleItem, false}, + {Credo.Check.Refactor.DoubleBooleanNegation, false}, + {Credo.Check.Refactor.ModuleDependencies, false}, + {Credo.Check.Refactor.PipeChainStart, false}, + {Credo.Check.Refactor.VariableRebinding, false}, + {Credo.Check.Warning.MapGetUnsafePass, false}, + {Credo.Check.Warning.UnsafeToAtom, false} + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + ] +} diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d304ff3 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,3 @@ +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.travis.yml b/.travis.yml index b22fe5e..7a05832 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,8 @@ matrix: elixir: 1.6 - otp_release: 21.0 elixir: 1.7 + - otp_release: 21.0 + elixir: 1.8 env: GLOBAL: @@ -28,4 +30,6 @@ env: sudo: false after_script: + - mix format --check-formatted + - mix credo - mix coveralls.travis diff --git a/README.md b/README.md index 2fc6ecd..31b7508 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ for more details. ## Debug mode -Some times its handy to see what's coming back from the response when getting +Sometimes it's handy to see what's coming back from the response when getting a token. You can configure OAuth2 to output the response like so: ```elixir diff --git a/config/config.exs b/config/config.exs index a2d3221..b3eb537 100644 --- a/config/config.exs +++ b/config/config.exs @@ -3,7 +3,9 @@ use Mix.Config config :logger, level: :debug config :oauth2, - client_id: "0bee1126b1a1381d9cab60bcd52349484451808a", # first commit sha of this library - client_secret: "f715d64092fe81c396ac383e97f8a7eca40e7c89", #second commit sha + # first commit sha of this library + client_id: "0bee1126b1a1381d9cab60bcd52349484451808a", + # second commit sha + client_secret: "f715d64092fe81c396ac383e97f8a7eca40e7c89", redirect_uri: "http://example.com/auth/callback", request_opts: [] diff --git a/lib/oauth2.ex b/lib/oauth2.ex index b460815..fb0afdd 100644 --- a/lib/oauth2.ex +++ b/lib/oauth2.ex @@ -2,7 +2,7 @@ defmodule OAuth2 do @moduledoc """ The OAuth2 specification - http://tools.ietf.org/html/rfc6749 + [RFC6749](http://tools.ietf.org/html/rfc6749) The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on @@ -12,13 +12,13 @@ defmodule OAuth2 do ## API - Current implemented strategies: + Currently implemented strategies: - Authorization Code - Password - Client Credentials - #### Authorization Code Flow (AuthCode Strategy) + ### Authorization Code Flow (AuthCode Strategy) Initialize a client with your `client_id`, `client_secret`, and `site`. diff --git a/lib/oauth2/access_token.ex b/lib/oauth2/access_token.ex index 6d2238a..493474d 100644 --- a/lib/oauth2/access_token.ex +++ b/lib/oauth2/access_token.ex @@ -14,19 +14,20 @@ defmodule OAuth2.AccessToken do @standard ["access_token", "refresh_token", "expires_in", "token_type"] - @type access_token :: binary + @type access_token :: binary @type refresh_token :: binary | nil - @type expires_at :: integer - @type token_type :: binary - @type other_params :: %{binary => binary} - @type body :: binary | map + @type expires_at :: integer + @type token_type :: binary + @type other_params :: %{binary => binary} + @type body :: binary | map | list @type t :: %__MODULE__{ - access_token: access_token, - refresh_token: refresh_token, - expires_at: expires_at, - token_type: token_type, - other_params: other_params} + access_token: access_token, + refresh_token: refresh_token, + expires_at: expires_at, + token_type: token_type, + other_params: other_params + } defstruct access_token: "", refresh_token: nil, @@ -58,13 +59,13 @@ defmodule OAuth2.AccessToken do def new(response) when is_map(response) do {std, other} = Map.split(response, @standard) - struct(AccessToken, [ - access_token: std["access_token"], + struct(AccessToken, + access_token: std["access_token"], refresh_token: std["refresh_token"], - expires_at: (std["expires_in"] || other["expires"]) |> expires_at, - token_type: std["token_type"] |> normalize_token_type(), - other_params: other - ]) + expires_at: (std["expires_in"] || other["expires"]) |> expires_at, + token_type: std["token_type"] |> normalize_token_type(), + other_params: other + ) end @doc """ @@ -72,7 +73,7 @@ defmodule OAuth2.AccessToken do Returns `true` unless `expires_at` is `nil`. """ - @spec expires?(AccessToken.t) :: boolean + @spec expires?(AccessToken.t()) :: boolean def expires?(%AccessToken{expires_at: nil} = _token), do: false def expires?(_), do: true @@ -87,12 +88,14 @@ defmodule OAuth2.AccessToken do Returns a unix timestamp based on now + expires_at (in seconds). """ def expires_at(nil), do: nil + def expires_at(val) when is_binary(val) do val - |> Integer.parse + |> Integer.parse() |> elem(0) |> expires_at end + def expires_at(int), do: unix_now() + int defp normalize_token_type(nil), do: "Bearer" diff --git a/lib/oauth2/client.ex b/lib/oauth2/client.ex index 87e2ba5..5df126d 100644 --- a/lib/oauth2/client.ex +++ b/lib/oauth2/client.ex @@ -29,41 +29,41 @@ defmodule OAuth2.Client do response = OAuth2.Client.post!(client, "/some/other/resources", %{foo: "bar"}) """ - alias OAuth2.{AccessToken, Client, Error, Response, Request} + alias OAuth2.{AccessToken, Client, Error, Request, Response} @type authorize_url :: binary - @type body :: any - @type client_id :: binary + @type body :: any + @type client_id :: binary @type client_secret :: binary - @type headers :: [{binary, binary}] - @type param :: binary | %{binary => param} | [param] - @type params :: %{binary => param} | Keyword.t - @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 - @type token_method :: :post | :get | atom - @type token_url :: binary + @type headers :: [{binary, binary}] + @type param :: binary | %{binary => param} | [param] + @type params :: %{binary => param} | Keyword.t() + @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 + @type token_method :: :post | :get | atom + @type token_url :: binary @type t :: %Client{ - authorize_url: authorize_url, - client_id: client_id, - client_secret: client_secret, - headers: headers, - params: params, - redirect_uri: redirect_uri, - ref: ref, - request_opts: request_opts, - serializers: serializers, - site: site, - strategy: strategy, - token: token, - token_method: token_method, - token_url: token_url - } + authorize_url: authorize_url, + client_id: client_id, + client_secret: client_secret, + headers: headers, + params: params, + redirect_uri: redirect_uri, + ref: ref, + request_opts: request_opts, + serializers: serializers, + site: site, + strategy: strategy, + token: token, + token_method: token_method, + token_url: token_url + } defstruct authorize_url: "/oauth/authorize", client_id: "", @@ -126,7 +126,7 @@ defmodule OAuth2.Client do [hackney documentation]: https://github.com/benoitc/hackney/blob/master/doc/hackney.md#request5 """ - @spec new(t, Keyword.t) :: t + @spec new(t, Keyword.t()) :: t def new(client \\ %Client{}, opts) do {token, opts} = Keyword.pop(opts, :token) {req_opts, opts} = Keyword.pop(opts, :request_opts, []) @@ -149,7 +149,7 @@ defmodule OAuth2.Client do The key can be a `string` or an `atom`. Atoms are automatically convert to strings. """ - @spec put_param(t, String.t | atom, any) :: t + @spec put_param(t, String.t() | atom, any) :: t def put_param(%Client{params: params} = client, key, value) do %{client | params: Map.put(params, "#{key}", value)} end @@ -159,9 +159,11 @@ 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, "#{k}", v) - end) + params = + Enum.reduce(params, %{}, fn {k, v}, acc -> + Map.put(acc, "#{k}", v) + end) + %{client | params: Map.merge(client.params, params)} end @@ -171,7 +173,7 @@ defmodule OAuth2.Client do """ @spec put_header(t, binary, binary) :: t def put_header(%Client{headers: headers} = client, key, value) - when is_binary(key) and is_binary(value) do + when is_binary(key) and is_binary(value) do key = String.downcase(key) %{client | headers: List.keystore(headers, key, 0, {key, value})} end @@ -181,7 +183,8 @@ 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 + + def put_headers(%Client{} = client, [{k, v} | rest]) do client |> put_header(k, v) |> put_headers(rest) @@ -219,7 +222,7 @@ defmodule OAuth2.Client do """ @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 + when is_binary(mime) and is_atom(module) do %Client{client | serializers: Map.put(serializers, mime, module)} end @@ -266,7 +269,8 @@ defmodule OAuth2.Client do * `:proxy` - a proxy to be used for the request; it can be a regular url or a `{host, proxy}` tuple """ - @spec get_token(t, params, headers, Keyword.t) :: {:ok, Client.t} | {:error, Response.t} | {:error, Error.t} + @spec get_token(t, params, headers, Keyword.t()) :: + {:ok, Client.t()} | {:error, Response.t()} | {:error, Error.t()} def get_token(%{token_method: method} = client, params \\ [], headers \\ [], opts \\ []) do {client, url} = token_url(client, params, headers) @@ -274,6 +278,7 @@ defmodule OAuth2.Client do {:ok, response} -> token = AccessToken.new(response.body) {:ok, %{client | headers: [], params: %{}, token: token}} + {:error, error} -> {:error, error} end @@ -283,22 +288,26 @@ defmodule OAuth2.Client do Same as `get_token/4` but raises `OAuth2.Error` if an error occurs during the request. """ - @spec get_token!(t, params, headers, Keyword.t) :: Client.t | Error.t + @spec get_token!(t, params, headers, Keyword.t()) :: Client.t() | Error.t() def get_token!(client, params \\ [], headers \\ [], opts \\ []) do case get_token(client, params, headers, opts) do {:ok, client} -> client + {:error, %Response{status_code: code, headers: headers, body: body}} -> - raise %Error{reason: """ - Server responded with status: #{code} + raise %Error{ + reason: """ + Server responded with status: #{code} + + Headers: - Headers: + #{Enum.reduce(headers, "", fn {k, v}, acc -> acc <> "#{k}: #{v}\n" end)} + Body: - #{Enum.reduce(headers, "", fn {k, v}, acc -> acc <> "#{k}: #{v}\n" end)} - Body: + #{inspect(body)} + """ + } - #{inspect body} - """} {:error, error} -> raise error end @@ -307,12 +316,20 @@ defmodule OAuth2.Client do @doc """ Refreshes an existing access token using a refresh token. """ - @spec refresh_token(t, params, headers, Keyword.t) :: {:ok, Client.t} | {:error, Response.t} | {:error, Error.t} + @spec refresh_token(t, params, headers, Keyword.t()) :: + {:ok, Client.t()} | {:error, Response.t()} | {:error, Error.t()} def refresh_token(token, params \\ [], headers \\ [], opts \\ []) + def refresh_token(%Client{token: %{refresh_token: nil}}, _params, _headers, _opts) do {:error, %Error{reason: "Refresh token not available."}} end - def refresh_token(%Client{token: %{refresh_token: refresh_token}} = client, params, headers, opts) do + + def refresh_token( + %Client{token: %{refresh_token: refresh_token}} = client, + params, + headers, + opts + ) do refresh_client = %{client | strategy: OAuth2.Strategy.Refresh, token: nil} |> Client.put_param(:refresh_token, refresh_token) @@ -324,14 +341,16 @@ defmodule OAuth2.Client do else {:ok, put_in(client.token.refresh_token, refresh_token)} end - {:error, error} -> {:error, error} + + {:error, error} -> + {:error, error} end end @doc """ Calls `refresh_token/4` but raises `Error` if there an error occurs. """ - @spec refresh_token!(t, params, headers, Keyword.t) :: Client.t | Error.t + @spec refresh_token!(t, params, headers, Keyword.t()) :: Client.t() | Error.t() def refresh_token!(%Client{} = client, params \\ [], headers \\ [], opts \\ []) do case refresh_token(client, params, headers, opts) do {:ok, %Client{} = client} -> client @@ -351,7 +370,8 @@ defmodule OAuth2.Client do Makes a `GET` request to the given `url` using the `OAuth2.AccessToken` struct. """ - @spec get(t, binary, headers, Keyword.t) :: {:ok, Response.t} | {:error, Response.t} | {:error, Error.t} + @spec get(t, binary, headers, Keyword.t()) :: + {:ok, Response.t()} | {:error, Response.t()} | {:error, Error.t()} def get(%Client{} = client, url, headers \\ [], opts \\ []), do: Request.request(:get, client, url, "", headers, opts) @@ -359,7 +379,7 @@ defmodule OAuth2.Client do Same as `get/4` but returns a `OAuth2.Response` or `OAuth2.Error` exception if the request results in an error. """ - @spec get!(t, binary, headers, Keyword.t) :: Response.t | Error.t + @spec get!(t, binary, headers, Keyword.t()) :: Response.t() | Error.t() def get!(%Client{} = client, url, headers \\ [], opts \\ []), do: Request.request!(:get, client, url, "", headers, opts) @@ -367,7 +387,8 @@ defmodule OAuth2.Client do Makes a `PUT` request to the given `url` using the `OAuth2.AccessToken` struct. """ - @spec put(t, binary, body, headers, Keyword.t) :: {:ok, Response.t} | {:error, Response.t} | {:error, Error.t} + @spec put(t, binary, body, headers, Keyword.t()) :: + {:ok, Response.t()} | {:error, Response.t()} | {:error, Error.t()} def put(%Client{} = client, url, body \\ "", headers \\ [], opts \\ []), do: Request.request(:put, client, url, body, headers, opts) @@ -378,7 +399,7 @@ defmodule OAuth2.Client do An `OAuth2.Error` exception is raised if the request results in an error tuple (`{:error, reason}`). """ - @spec put!(t, binary, body, headers, Keyword.t) :: Response.t | Error.t + @spec put!(t, binary, body, headers, Keyword.t()) :: Response.t() | Error.t() def put!(%Client{} = client, url, body \\ "", headers \\ [], opts \\ []), do: Request.request!(:put, client, url, body, headers, opts) @@ -386,7 +407,8 @@ defmodule OAuth2.Client do Makes a `PATCH` request to the given `url` using the `OAuth2.AccessToken` struct. """ - @spec patch(t, binary, body, headers, Keyword.t) :: {:ok, Response.t} | {:error, Response.t} | {:error, Error.t} + @spec patch(t, binary, body, headers, Keyword.t()) :: + {:ok, Response.t()} | {:error, Response.t()} | {:error, Error.t()} def patch(%Client{} = client, url, body \\ "", headers \\ [], opts \\ []), do: Request.request(:patch, client, url, body, headers, opts) @@ -397,14 +419,15 @@ defmodule OAuth2.Client do An `OAuth2.Error` exception is raised if the request results in an error tuple (`{:error, reason}`). """ - @spec patch!(t, binary, body, headers, Keyword.t) :: Response.t | Error.t + @spec patch!(t, binary, body, headers, Keyword.t()) :: Response.t() | Error.t() def patch!(%Client{} = client, url, body \\ "", headers \\ [], opts \\ []), do: Request.request!(:patch, client, url, body, headers, opts) @doc """ Makes a `POST` request to the given URL using the `OAuth2.AccessToken`. """ - @spec post(t, binary, body, headers, Keyword.t) :: {:ok, Response.t} | {:error, Response.t} | {:error, Error.t} + @spec post(t, binary, body, headers, Keyword.t()) :: + {:ok, Response.t()} | {:error, Response.t()} | {:error, Error.t()} def post(%Client{} = client, url, body \\ "", headers \\ [], opts \\ []), do: Request.request(:post, client, url, body, headers, opts) @@ -415,14 +438,15 @@ defmodule OAuth2.Client do An `OAuth2.Error` exception is raised if the request results in an error tuple (`{:error, reason}`). """ - @spec post!(t, binary, body, headers, Keyword.t) :: Response.t | Error.t + @spec post!(t, binary, body, headers, Keyword.t()) :: Response.t() | Error.t() def post!(%Client{} = client, url, body \\ "", headers \\ [], opts \\ []), do: Request.request!(:post, client, url, body, headers, opts) @doc """ Makes a `DELETE` request to the given URL using the `OAuth2.AccessToken`. """ - @spec delete(t, binary, body, headers, Keyword.t) :: {:ok, Response.t} | {:error, Response.t} | {:error, Error.t} + @spec delete(t, binary, body, headers, Keyword.t()) :: + {:ok, Response.t()} | {:error, Response.t()} | {:error, Error.t()} def delete(%Client{} = client, url, body \\ "", headers \\ [], opts \\ []), do: Request.request(:delete, client, url, body, headers, opts) @@ -433,7 +457,7 @@ defmodule OAuth2.Client do An `OAuth2.Error` exception is raised if the request results in an error tuple (`{:error, reason}`). """ - @spec delete!(t, binary, body, headers, Keyword.t) :: Response.t | Error.t + @spec delete!(t, binary, body, headers, Keyword.t()) :: Response.t() | Error.t() def delete!(%Client{} = client, url, body \\ "", headers \\ [], opts \\ []), do: Request.request!(:delete, client, url, body, headers, opts) @@ -454,11 +478,13 @@ defmodule OAuth2.Client do |> to_url(:token_url) end - defp token_post_header(%Client{token_method: :post} = client), do: - put_header(client, "content-type", "application/x-www-form-urlencoded") + defp token_post_header(%Client{token_method: :post} = client), + do: put_header(client, "content-type", "application/x-www-form-urlencoded") + defp token_post_header(%Client{} = client), do: client defp endpoint(client, <<"/"::utf8, _::binary>> = endpoint), do: client.site <> endpoint + defp endpoint(_client, endpoint), do: endpoint end diff --git a/lib/oauth2/error.ex b/lib/oauth2/error.ex index 43ac4c6..8ad15dd 100644 --- a/lib/oauth2/error.ex +++ b/lib/oauth2/error.ex @@ -2,12 +2,12 @@ defmodule OAuth2.Error do @moduledoc false @type t :: %__MODULE__{ - reason: binary - } + reason: binary + } defexception [:reason] def message(%__MODULE__{reason: :econnrefused}), do: "Connection refused" def message(%__MODULE__{reason: reason}) when is_binary(reason), do: reason - def message(%__MODULE__{reason: reason}), do: inspect reason + def message(%__MODULE__{reason: reason}), do: inspect(reason) end diff --git a/lib/oauth2/request.ex b/lib/oauth2/request.ex index 0dfb40c..8635e9b 100644 --- a/lib/oauth2/request.ex +++ b/lib/oauth2/request.ex @@ -11,11 +11,11 @@ defmodule OAuth2.Request do @doc """ Makes a request of given type to the given URL using the `OAuth2.AccessToken`. """ - @spec request(atom, Client.t, binary, body, Client.headers, Keyword.t) - :: {:ok, Response.t} | {:error, Response.t} | {:error, Error.t} + @spec request(atom, Client.t(), binary, body, Client.headers(), Keyword.t()) :: + {:ok, Response.t()} | {:ok, reference} | {:error, Response.t()} | {:error, Error.t()} def request(method, %Client{} = client, url, body, headers, opts) do url = client |> process_url(url) |> process_params(opts[:params]) - headers = req_headers(client, headers) |> Enum.uniq + headers = req_headers(client, headers) |> Enum.uniq() content_type = content_type(headers) serializer = Client.get_serializer(client, content_type) body = encode_request_body(body, content_type, serializer) @@ -25,21 +25,24 @@ defmodule OAuth2.Request do if Application.get_env(:oauth2, :debug) do Logger.debug(""" OAuth2 Provider Request - url: #{inspect url} - method: #{inspect method} - headers: #{inspect headers} - body: #{inspect body} - req_opts: #{inspect req_opts} + url: #{inspect(url)} + method: #{inspect(method)} + headers: #{inspect(headers)} + body: #{inspect(body)} + req_opts: #{inspect(req_opts)} """) end 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(client, status, headers, ref) + {:ok, status, headers, body} when is_binary(body) -> process_body(client, status, headers, body) + {:error, reason} -> {:error, %Error{reason: reason}} end @@ -52,22 +55,26 @@ defmodule OAuth2.Request do An `OAuth2.Error` exception is raised if the request results in an error tuple (`{:error, reason}`). """ - @spec request!(atom, Client.t, binary, body, Client.headers, Keyword.t) :: Response.t + @spec request!(atom, Client.t(), binary, body, Client.headers(), Keyword.t()) :: Response.t() def request!(method, %Client{} = client, url, body, headers, opts) do case request(method, client, url, body, headers, opts) do {:ok, resp} -> resp + {:error, %Response{status_code: code, headers: headers, body: body}} -> - raise %Error{reason: """ - Server responded with status: #{code} + raise %Error{ + reason: """ + Server responded with status: #{code} - Headers: + Headers: - #{Enum.reduce(headers, "", fn {k, v}, acc -> acc <> "#{k}: #{v}\n" end)} - Body: + #{Enum.reduce(headers, "", fn {k, v}, acc -> acc <> "#{k}: #{v}\n" end)} + Body: + + #{inspect(body)} + """ + } - #{inspect body} - """} {:error, error} -> raise error end @@ -85,15 +92,19 @@ defmodule OAuth2.Request do case :hackney.body(ref) do {:ok, body} -> process_body(client, status, headers, body) + {:error, reason} -> {:error, %Error{reason: reason}} end end + 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} + status when status in 400..599 -> {:error, resp} end @@ -101,11 +112,13 @@ defmodule OAuth2.Request do defp process_params(url, nil), do: url + defp process_params(url, params), do: url <> "?" <> URI.encode_query(params) defp req_headers(%Client{token: nil} = client, headers), do: headers ++ client.headers + defp req_headers(%Client{token: token} = client, headers), do: [authorization_header(token) | headers] ++ client.headers @@ -116,6 +129,7 @@ defmodule OAuth2.Request do case List.keyfind(headers, "accept", 0) do {"accept", _} -> headers + nil -> [{"accept", content_type} | headers] end @@ -123,11 +137,14 @@ defmodule OAuth2.Request do 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, _mime, nil) do body end + defp encode_request_body(body, _mime, serializer) do serializer.encode!(body) end diff --git a/lib/oauth2/response.ex b/lib/oauth2/response.ex index 5073a6a..1d09ac3 100644 --- a/lib/oauth2/response.ex +++ b/lib/oauth2/response.ex @@ -15,18 +15,19 @@ defmodule OAuth2.Response do alias OAuth2.Client @type status_code :: integer - @type headers :: list - @type body :: binary | map | list + @type headers :: [{binary, binary}] + @type body :: binary | map | list @type t :: %__MODULE__{ - status_code: status_code, - headers: headers, - body: body - } + status_code: status_code, + headers: headers, + body: body + } defstruct status_code: nil, headers: [], body: nil @doc false + @spec new(Client.t(), integer, headers, body) :: t def new(client, code, headers, body) do headers = process_headers(headers) content_type = content_type(headers) @@ -35,7 +36,7 @@ defmodule OAuth2.Response do resp = %__MODULE__{status_code: code, headers: headers, body: body} if Application.get_env(:oauth2, :debug) do - Logger.debug("OAuth2 Provider Response #{inspect resp}") + Logger.debug("OAuth2 Provider Response #{inspect(resp)}") end resp @@ -47,9 +48,11 @@ defmodule OAuth2.Response do defp decode_response_body("", _type, _), do: "" defp decode_response_body(" ", _type, _), do: "" + defp decode_response_body(body, _type, serializer) when serializer != nil do serializer.decode!(body) end + # Facebook sends text/plain tokens!? defp decode_response_body(body, "text/plain", _) do case URI.decode_query(body) do @@ -57,9 +60,11 @@ defmodule OAuth2.Response do _ -> body end end + 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 diff --git a/lib/oauth2/strategy.ex b/lib/oauth2/strategy.ex index 3a19092..56cb13a 100644 --- a/lib/oauth2/strategy.ex +++ b/lib/oauth2/strategy.ex @@ -78,10 +78,10 @@ defmodule OAuth2.Strategy do |> merge_params(params) end """ - @callback authorize_url(Client.t, Client.params) :: Client.t + @callback authorize_url(Client.t(), Client.params()) :: Client.t() @doc """ - Builds the URL to token endpoint. + Builds the URL to the token endpoint. ## Example @@ -96,7 +96,7 @@ defmodule OAuth2.Strategy do |> put_headers(headers) end """ - @callback get_token(Client.t, Client.params, Client.headers) :: Client.t + @callback get_token(Client.t(), Client.params(), Client.headers()) :: Client.t() defmacro __using__(_) do quote do diff --git a/lib/oauth2/strategy/auth_code.ex b/lib/oauth2/strategy/auth_code.ex index acf1e7c..5fed088 100644 --- a/lib/oauth2/strategy/auth_code.ex +++ b/lib/oauth2/strategy/auth_code.ex @@ -30,6 +30,7 @@ defmodule OAuth2.Strategy.AuthCode do The authorization URL endpoint of the provider. params additional query parameters for the URL """ + @impl true def authorize_url(client, params) do client |> put_param(:response_type, "code") @@ -41,11 +42,12 @@ defmodule OAuth2.Strategy.AuthCode do @doc """ Retrieve an access token given the specified validation code. """ + @impl true def get_token(client, params, headers) do {code, params} = Keyword.pop(params, :code, client.params["code"]) unless code do - raise OAuth2.Error, reason: "Missing required key `code` for `#{inspect __MODULE__}`" + raise OAuth2.Error, reason: "Missing required key `code` for `#{inspect(__MODULE__)}`" end client @@ -58,4 +60,3 @@ defmodule OAuth2.Strategy.AuthCode do |> put_headers(headers) end end - diff --git a/lib/oauth2/strategy/client_credentials.ex b/lib/oauth2/strategy/client_credentials.ex index 43335c2..288291a 100644 --- a/lib/oauth2/strategy/client_credentials.ex +++ b/lib/oauth2/strategy/client_credentials.ex @@ -20,6 +20,7 @@ defmodule OAuth2.Strategy.ClientCredentials do @doc """ Not used for this strategy. """ + @impl true def authorize_url(_client, _params) do raise OAuth2.Error, reason: "This strategy does not implement `authorize_url`." end @@ -27,6 +28,7 @@ defmodule OAuth2.Strategy.ClientCredentials do @doc """ Retrieve an access token given the specified strategy. """ + @impl true def get_token(client, params, headers) do {auth_scheme, params} = Keyword.pop(params, :auth_scheme, "auth_header") @@ -37,7 +39,7 @@ defmodule OAuth2.Strategy.ClientCredentials do |> put_headers(headers) end - defp auth_scheme(client, "auth_header"), do: basic_auth(client) + defp auth_scheme(client, "auth_header"), do: basic_auth(client) defp auth_scheme(client, "request_body"), do: request_body(client) defp request_body(client) do @@ -46,4 +48,3 @@ defmodule OAuth2.Strategy.ClientCredentials do |> put_param(:client_secret, client.client_secret) end end - diff --git a/lib/oauth2/strategy/password.ex b/lib/oauth2/strategy/password.ex index 3f1fa40..2bf7150 100644 --- a/lib/oauth2/strategy/password.ex +++ b/lib/oauth2/strategy/password.ex @@ -25,6 +25,7 @@ defmodule OAuth2.Strategy.Password do @doc """ Not used for this strategy. """ + @impl true def authorize_url(_client, _params) do raise OAuth2.Error, reason: "This strategy does not implement `authorize_url`." end @@ -32,12 +33,14 @@ defmodule OAuth2.Strategy.Password do @doc """ Retrieve an access token given the specified End User username and password. """ + @impl true def get_token(client, params, headers) do {username, params} = Keyword.pop(params, :username, client.params["username"]) {password, params} = Keyword.pop(params, :password, client.params["password"]) unless username && password do - raise OAuth2.Error, reason: "Missing required keys `username` and `password` for #{inspect __MODULE__}" + raise OAuth2.Error, + reason: "Missing required keys `username` and `password` for #{inspect(__MODULE__)}" end client diff --git a/lib/oauth2/strategy/refresh.ex b/lib/oauth2/strategy/refresh.ex index 9f07449..8761ee7 100644 --- a/lib/oauth2/strategy/refresh.ex +++ b/lib/oauth2/strategy/refresh.ex @@ -27,6 +27,7 @@ defmodule OAuth2.Strategy.Refresh do @doc """ Not used for this strategy. """ + @impl true def authorize_url(_client, _params) do raise OAuth2.Error, reason: "This strategy does not implement `authorize_url`." end @@ -34,11 +35,13 @@ defmodule OAuth2.Strategy.Refresh do @doc """ Refresh an access token given the specified validation code. """ + @impl true def get_token(client, params, headers) do {token, params} = Keyword.pop(params, :refresh_token, client.params["refresh_token"]) unless token do - raise OAuth2.Error, reason: "Missing required key `refresh_token` for `#{inspect __MODULE__}`" + raise OAuth2.Error, + reason: "Missing required key `refresh_token` for `#{inspect(__MODULE__)}`" end client diff --git a/lib/oauth2/util.ex b/lib/oauth2/util.ex index b4281a2..cf67107 100644 --- a/lib/oauth2/util.ex +++ b/lib/oauth2/util.ex @@ -1,17 +1,20 @@ defmodule OAuth2.Util do @moduledoc false + @spec unix_now :: integer def unix_now do - {mega, sec, _micro} = :os.timestamp - (mega * 1_000_000) + sec + {mega, sec, _micro} = :os.timestamp() + mega * 1_000_000 + sec end + @spec content_type([{binary, binary}]) :: binary def content_type(headers) do case get_content_type(headers) do {_, content_type} -> content_type |> remove_params |> parse_content_type + nil -> "application/json" end @@ -26,8 +29,9 @@ defmodule OAuth2.Util do case String.split(content_type, "/") do [type, subtype] -> type <> "/" <> subtype - [bad_type] -> - raise OAuth2.Error, reason: "bad content-type: #{bad_type}" + + _ -> + raise OAuth2.Error, reason: "bad content-type: #{content_type}" end end diff --git a/mix.exs b/mix.exs index 68a11e8..b7c9dba 100644 --- a/mix.exs +++ b/mix.exs @@ -4,21 +4,24 @@ defmodule OAuth2.Mixfile do @version "1.0.1" def project do - [app: :oauth2, - name: "OAuth2", - version: @version, - elixir: "~> 1.2", - deps: deps(), - package: package(), - description: description(), - docs: docs(), - elixirc_paths: elixirc_paths(Mix.env), - test_coverage: [tool: ExCoveralls], - preferred_cli_env: [ - coveralls: :test, - "coveralls.detail": :test, - docs: :dev - ]] + [ + app: :oauth2, + name: "OAuth2", + version: @version, + elixir: "~> 1.2", + deps: deps(), + package: package(), + description: description(), + docs: docs(), + elixirc_paths: elixirc_paths(Mix.env()), + test_coverage: [tool: ExCoveralls], + preferred_cli_env: [ + coveralls: :test, + "coveralls.detail": :test, + "coveralls.html": :test, + docs: :dev + ] + ] end def application do @@ -26,17 +29,20 @@ defmodule OAuth2.Mixfile do end defp deps do - [{:hackney, "~> 1.13"}, + [ + {:hackney, "~> 1.13"}, - # Test dependencies - {: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}, + # Test dependencies + {:jason, "~> 1.0", only: [:dev, :test]}, + {:bypass, "~> 0.9", only: :test}, + {:plug_cowboy, "~> 1.0", only: :test}, + {:excoveralls, "~> 0.9", only: :test}, + {:credo, "~> 1.1.0", only: [:dev, :test], runtime: false}, + {:dialyxir, "~> 1.0.0-rc.6", only: [:dev], runtime: false}, - # Docs dependencies - {:ex_doc, "~> 0.19", only: :dev}] + # Docs dependencies + {:ex_doc, "~> 0.19", only: :dev} + ] end defp description do @@ -44,17 +50,21 @@ defmodule OAuth2.Mixfile do end defp docs do - [extras: ["README.md"], - main: "readme", - source_ref: "v#{@version}", - source_url: "https://github.com/scrogson/oauth2"] + [ + extras: ["README.md"], + main: "readme", + source_ref: "v#{@version}", + source_url: "https://github.com/scrogson/oauth2" + ] end defp package do - [files: ["lib", "mix.exs", "README.md", "LICENSE"], - maintainers: ["Sonny Scroggin"], - licenses: ["MIT"], - links: %{github: "https://github.com/scrogson/oauth2"}] + [ + files: ["lib", "mix.exs", "README.md", "LICENSE"], + maintainers: ["Sonny Scroggin"], + licenses: ["MIT"], + links: %{github: "https://github.com/scrogson/oauth2"} + ] end defp elixirc_paths(:test), do: ["lib", "test/support"] diff --git a/mix.lock b/mix.lock index 61fa37a..c64e8c5 100644 --- a/mix.lock +++ b/mix.lock @@ -1,26 +1,29 @@ %{ + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, "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"}, + "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [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.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"}, + "credo": {:hex, :credo, "1.1.0", "e0c07b2fd7e2109495f582430a1bc96b2c71b7d94c59dfad120529f65f19872f", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "dialyxir": {:hex, :dialyxir, "1.0.0-rc.6", "78e97d9c0ff1b5521dd68041193891aebebce52fc3b93463c0a6806874557d7d", [:mix], [{:erlex, "~> 0.2.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"}, + "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, + "erlex": {:hex, :erlex, "0.2.2", "cb0e6878fdf86dc63509eaf2233a71fa73fc383c8362c8ff8e8b6f0c2bb7017c", [:mix], [], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, + "excoveralls": {:hex, :excoveralls, "0.11.1", "dd677fbdd49114fdbdbf445540ec735808250d56b011077798316505064edb2c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.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"}, + "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, "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"}, - "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"}, + "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, + "plug": {:hex, :plug, "1.8.2", "0bcce1daa420f189a6491f3940cc77ea7fb1919761175c9c3b59800d897440fc", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "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"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, } diff --git a/test/oauth2/access_token_test.exs b/test/oauth2/access_token_test.exs index b786ba0..fb9a8fa 100644 --- a/test/oauth2/access_token_test.exs +++ b/test/oauth2/access_token_test.exs @@ -12,7 +12,14 @@ defmodule OAuth2.AccessTokenTest do end test "new with 'expires_in' param" do - response = Response.new(%Client{}, 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 +28,14 @@ defmodule OAuth2.AccessTokenTest do end test "new with 'expires' param" do - response = Response.new(%Client{}, 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 +44,14 @@ defmodule OAuth2.AccessTokenTest do end test "new from text/plain content-type" do - response = Response.new(%Client{}, 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() @@ -53,5 +74,4 @@ defmodule OAuth2.AccessTokenTest do assert AccessToken.expires_at(3600) == unix_now() + 3600 assert AccessToken.expires_at("3600") == unix_now() + 3600 end - end diff --git a/test/oauth2/client_test.exs b/test/oauth2/client_test.exs index 205486d..4bbd880 100644 --- a/test/oauth2/client_test.exs +++ b/test/oauth2/client_test.exs @@ -10,17 +10,18 @@ defmodule OAuth2.ClientTest do alias OAuth2.Response setup do - server = Bypass.open + server = Bypass.open() client = build_client(site: bypass_server(server)) client_with_token = tokenize_client(client) async_client = async_client(client) basic_auth = Base.encode64(client.client_id <> ":" <> client.client_secret) - {:ok, basic_auth: basic_auth, - client: client, - server: server, - client_with_token: client_with_token, - async_client: async_client} + {:ok, + basic_auth: basic_auth, + client: client, + server: server, + client_with_token: client_with_token, + async_client: async_client} end test "authorize_url!", %{client: client, server: server} do @@ -36,25 +37,29 @@ defmodule OAuth2.ClientTest do end test "get_token, get_token!", %{client: client, server: server} do - bypass server, "POST", "/oauth/token", fn conn -> + bypass(server, "POST", "/oauth/token", fn conn -> assert conn.query_string == "" send_resp(conn, 200, ~s({"access_token":"test1234"})) - end + end) + + assert {:ok, client} = + Client.get_token(client, [code: "code1234"], [{"accept", "application/json"}]) - assert {:ok, client} = Client.get_token(client, [code: "code1234"], [{"accept", "application/json"}]) assert client.token.access_token == "test1234" - assert %Client{} = Client.get_token!(client, [code: "code1234"], [{"accept", "application/json"}]) + + assert %Client{} = + Client.get_token!(client, [code: "code1234"], [{"accept", "application/json"}]) end test "get_token, get_token! when `:token_method` is `:get`", %{client: client, server: server} do client = %{client | token_method: :get} - bypass server, "GET", "/oauth/token", fn conn -> + bypass(server, "GET", "/oauth/token", fn conn -> refute conn.query_string == "" assert conn.query_params["code"] == "code1234" assert conn.query_params["redirect_uri"] send_resp(conn, 200, ~s({"access_token":"test1234","token_type":"bearer"})) - end + end) assert {:ok, %Client{token: token}} = Client.get_token(client, code: "code1234") assert token.access_token == "test1234" @@ -66,10 +71,10 @@ defmodule OAuth2.ClientTest do code = [code: "code1234"] headers = [{"accept", "application/json"}] - bypass server, "POST", "/oauth/token", fn conn -> + bypass(server, "POST", "/oauth/token", fn conn -> assert conn.query_string == "" send_resp(conn, 500, ~s({"error":"missing_client_id"})) - end + end) assert {:error, error} = Client.get_token(client, code, headers) assert %Response{body: body, status_code: 500} = error @@ -80,16 +85,23 @@ defmodule OAuth2.ClientTest do end end - test "refresh_token and refresh_token! with a POST", %{basic_auth: base64, server: server, client_with_token: client} do - bypass server, "POST", "/oauth/token", fn conn -> + test "refresh_token and refresh_token! with a POST", %{ + basic_auth: base64, + server: server, + client_with_token: client + } do + bypass(server, "POST", "/oauth/token", fn conn -> assert get_req_header(conn, "authorization") == ["Basic #{base64}"] assert get_req_header(conn, "accept") == ["application/json"] assert get_req_header(conn, "content-type") == ["application/x-www-form-urlencoded"] conn |> put_resp_header("content-type", "application/json") - |> send_resp(200, ~s({"access_token":"new-access-token","refresh_token":"new-refresh-token"})) - end + |> send_resp( + 200, + ~s({"access_token":"new-access-token","refresh_token":"new-refresh-token"}) + ) + end) {:error, error} = Client.refresh_token(client) assert error.reason =~ ~r/token not available/ @@ -100,13 +112,21 @@ defmodule OAuth2.ClientTest do token = client.token client = %{client | token: %{token | refresh_token: "abcdefg"}} - assert {:ok, client} = Client.refresh_token(client, [], [{"accept", "application/json"}]) - assert client.token.access_token == "new-access-token" - assert client.token.refresh_token == "new-refresh-token" + assert {:ok, client_a} = Client.refresh_token(client, [], [{"accept", "application/json"}]) + assert client_a.token.access_token == "new-access-token" + assert client_a.token.refresh_token == "new-refresh-token" + + assert client_b = Client.refresh_token!(client, [], [{"accept", "application/json"}]) + assert client_b.token.access_token == "new-access-token" + assert client_b.token.refresh_token == "new-refresh-token" end - test "refresh token when response missing refresh_token", %{basic_auth: base64, server: server, client_with_token: client} do - bypass server, "POST", "/oauth/token", fn conn -> + test "refresh token when response missing refresh_token", %{ + basic_auth: base64, + server: server, + client_with_token: client + } do + bypass(server, "POST", "/oauth/token", fn conn -> assert get_req_header(conn, "authorization") == ["Basic #{base64}"] assert get_req_header(conn, "accept") == ["application/json"] assert get_req_header(conn, "content-type") == ["application/x-www-form-urlencoded"] @@ -114,7 +134,7 @@ defmodule OAuth2.ClientTest do conn |> put_resp_header("content-type", "application/json") |> send_resp(200, ~s({"access_token":"new-access-token"})) - end + end) token = client.token client = %{client | token: %{token | refresh_token: "old-refresh-token"}} @@ -123,9 +143,8 @@ defmodule OAuth2.ClientTest do assert client.token.refresh_token == "old-refresh-token" end - test "put_param, merge_params", %{client: client} do - assert Map.size(client.params) == 0 + assert map_size(client.params) == 0 client = put_param(client, :scope, "user,email") assert client.params["scope"] == "user,email" @@ -140,7 +159,10 @@ defmodule OAuth2.ClientTest do test "put_header, put_headers", %{client: client} do client = put_header(client, "accepts", "application/json") assert {"accepts", "application/json"} = List.keyfind(client.headers, "accepts", 0) - client = put_headers(client, [{"accepts", "application/xml"},{"content-type", "application/xml"}]) + + client = + put_headers(client, [{"accepts", "application/xml"}, {"content-type", "application/xml"}]) + assert {"accepts", "application/xml"} = List.keyfind(client.headers, "accepts", 0) assert {"content-type", "application/xml"} = List.keyfind(client.headers, "content-type", 0) end @@ -185,10 +207,25 @@ defmodule OAuth2.ClientTest do assert resp_body == body end + test "GET with with_body: true", %{server: server, client_with_token: client} do + bypass(server, "GET", "/api/user/1", [token: client.token], fn conn -> + json(conn, 200, %{id: 1}) + end) + + {:ok, result} = Client.get(client, "/api/user/1", [], with_body: true) + assert result.status_code == 200 + assert result.body["id"] == 1 + + result = Client.get!(client, "/api/user/1") + assert result.status_code == 200 + assert result.body["id"] == 1 + end + defp stream(ref, buffer \\ []) do receive do {:hackney_response, ^ref, :done} -> IO.iodata_to_binary(buffer) + {:hackney_response, ^ref, binary} -> stream(ref, buffer ++ [binary]) end @@ -199,9 +236,9 @@ defmodule OAuth2.ClientTest do test "POST", %{server: server, client_with_token: client} do title = "Totally awesome blog post" - bypass server, "POST", "/api/posts", [token: client.token], fn conn -> + bypass(server, "POST", "/api/posts", [token: client.token], fn conn -> json(conn, 200, %{id: 1, title: title}) - end + end) {:ok, result} = Client.post(client, "/api/posts", %{title: title}) assert result.status_code == 200 @@ -219,9 +256,9 @@ defmodule OAuth2.ClientTest do test "PUT", %{server: server, client_with_token: client} do title = "Totally awesome blog post!" - bypass server, "PUT", "/api/posts/1", [token: client.token], fn conn -> + bypass(server, "PUT", "/api/posts/1", [token: client.token], fn conn -> json(conn, 200, %{id: 1, title: title}) - end + end) {:ok, result} = Client.put(client, "/api/posts/1", %{id: 1, title: title}) assert result.status_code == 200 @@ -239,9 +276,9 @@ defmodule OAuth2.ClientTest do test "PATCH", %{server: server, client_with_token: client} do title = "Totally awesome blog post!" - bypass server, "PATCH", "/api/posts/1", [token: client.token], fn conn -> + bypass(server, "PATCH", "/api/posts/1", [token: client.token], fn conn -> json(conn, 200, %{id: 1, title: title}) - end + end) {:ok, result} = Client.patch(client, "/api/posts/1", %{id: 1, title: title}) assert result.status_code == 200 @@ -257,9 +294,9 @@ defmodule OAuth2.ClientTest do ## DELETE test "DELETE", %{server: server, client_with_token: client} do - bypass server, "DELETE", "/api/posts/1", [token: client.token], fn conn -> + bypass(server, "DELETE", "/api/posts/1", [token: client.token], fn conn -> json(conn, 204, "") - end + end) {:ok, result} = Client.delete(client, "/api/posts/1") assert result.status_code == 204 @@ -271,37 +308,43 @@ defmodule OAuth2.ClientTest do end test "params in opts turn into a query string", %{server: server, client_with_token: client} do - Bypass.expect server, fn conn -> + Bypass.expect(server, fn conn -> assert conn.query_string == "access_token=#{client.token.access_token}" send_resp(conn, 200, "") - end + end) - assert {:ok, _} = Client.get(client, "/me", [], params: [access_token: client.token.access_token]) + assert {:ok, _} = + Client.get(client, "/me", [], params: [access_token: client.token.access_token]) end test "follow redirects", %{server: server, client_with_token: client} do - Bypass.expect server, fn conn -> + Bypass.expect(server, fn conn -> case conn.path_info do ["old"] -> conn |> put_resp_header("location", "http://localhost:#{server.port}/new") |> send_resp(302, "") + ["new"] -> conn |> put_resp_content_type("text/html") |> send_resp(200, "ok") end - end + end) - assert {:ok, %{body: "ok", status_code: 200}} = Client.get(client, "/old", [], params: [access_token: client.token.access_token], follow_redirect: true) + assert {:ok, %{body: "ok", status_code: 200}} = + Client.get(client, "/old", [], + params: [access_token: client.token.access_token], + follow_redirect: true + ) end test "get returning 401 with no content", %{server: server, client_with_token: client} do - bypass server, "GET", "/api/user", [token: client.token], fn conn -> + bypass(server, "GET", "/api/user", [token: client.token], fn conn -> conn |> put_resp_header("content-type", "text/html") |> send_resp(401, " ") - end + end) {:error, result} = Client.get(client, "/api/user") assert result.status_code == 401 @@ -309,9 +352,9 @@ defmodule OAuth2.ClientTest do end test "bang functions raise errors", %{server: server, client: client} do - Bypass.expect server, fn conn -> + Bypass.expect(server, fn conn -> json(conn, 400, %{error: "error"}) - end + end) assert_raise OAuth2.Error, ~r/Server responded with status: 400/, fn -> Client.get!(client, "/api/error") diff --git a/test/oauth2/response_test.exs b/test/oauth2/response_test.exs index 0aa46e2..757d910 100644 --- a/test/oauth2/response_test.exs +++ b/test/oauth2/response_test.exs @@ -8,7 +8,11 @@ defmodule OAuth2.ResponseTest do 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) + 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") diff --git a/test/oauth2/strategy/auth_code_test.exs b/test/oauth2/strategy/auth_code_test.exs index ef91ff3..6f715c9 100644 --- a/test/oauth2/strategy/auth_code_test.exs +++ b/test/oauth2/strategy/auth_code_test.exs @@ -1,5 +1,4 @@ defmodule OAuth2.Strategy.AuthCodeTest do - use ExUnit.Case, async: true use Plug.Test @@ -9,7 +8,7 @@ defmodule OAuth2.Strategy.AuthCodeTest do alias OAuth2.Strategy.AuthCode setup do - server = Bypass.open + server = Bypass.open() client = build_client(strategy: AuthCode, site: bypass_server(server)) {:ok, client: client, server: server} end @@ -28,7 +27,7 @@ defmodule OAuth2.Strategy.AuthCodeTest do access_token = "access-token-1234" base64 = Base.encode64(client.client_id <> ":" <> client.client_secret) - Bypass.expect server, fn conn -> + Bypass.expect(server, fn conn -> assert conn.method == "POST" assert conn.request_path == "/oauth/token" assert get_req_header(conn, "content-type") == ["application/x-www-form-urlencoded"] @@ -43,9 +42,9 @@ defmodule OAuth2.Strategy.AuthCodeTest do assert body["redirect_uri"] == client.redirect_uri send_resp(conn, 200, ~s({"access_token":"#{access_token}"})) - end + end) - assert {:ok, %Client{token: token}} = Client.get_token(client, [code: code]) + assert {:ok, %Client{token: token}} = Client.get_token(client, code: code) assert token.access_token == access_token end diff --git a/test/oauth2/strategy/client_credentials_test.exs b/test/oauth2/strategy/client_credentials_test.exs index e8fcc75..39bac08 100644 --- a/test/oauth2/strategy/client_credentials_test.exs +++ b/test/oauth2/strategy/client_credentials_test.exs @@ -2,12 +2,12 @@ defmodule OAuth2.Strategy.ClientCredentialsTest do use ExUnit.Case, async: true use Plug.Test - alias OAuth2.Strategy.ClientCredentials alias OAuth2.Client + alias OAuth2.Strategy.ClientCredentials import OAuth2.TestHelpers setup do - server = Bypass.open + server = Bypass.open() client = build_client(strategy: ClientCredentials, site: bypass_server(server)) {:ok, client: client, server: server} end @@ -26,12 +26,17 @@ defmodule OAuth2.Strategy.ClientCredentialsTest do end test "get_token: Duplicated auth_header ", %{client: client, server: server} do - Bypass.expect server, fn conn -> + Bypass.expect(server, fn conn -> base64 = Base.encode64(client.client_id <> ":" <> client.client_secret) assert get_req_header(conn, "authorization") == ["Basic #{base64}"] - send_resp conn, 200, ~s({"access_token": "123456==", "token_type": "bearer", "expires_in": "999" }) - end + send_resp( + conn, + 200, + ~s({"access_token": "123456==", "token_type": "bearer", "expires_in": "999" }) + ) + end) + client = Client.get_token!(client) assert client.token.access_token == "123456==" diff --git a/test/oauth2/strategy/password_test.exs b/test/oauth2/strategy/password_test.exs index 428cc4f..91e450c 100644 --- a/test/oauth2/strategy/password_test.exs +++ b/test/oauth2/strategy/password_test.exs @@ -24,7 +24,8 @@ defmodule OAuth2.Strategy.PasswordTest do assert client.params["password"] == "password" assert client.params["grant_type"] == "password" - assert List.keyfind(client.headers, "authorization", 0) == {"authorization", "Basic #{base64}"} + assert List.keyfind(client.headers, "authorization", 0) == + {"authorization", "Basic #{base64}"} end test "get_token when username and password updated via put_param", %{client: client} do diff --git a/test/oauth2/strategy/refresh_test.exs b/test/oauth2/strategy/refresh_test.exs index 3a4b8f1..7e69a75 100644 --- a/test/oauth2/strategy/refresh_test.exs +++ b/test/oauth2/strategy/refresh_test.exs @@ -1,5 +1,4 @@ defmodule OAuth2.Strategy.RefreshTest do - use ExUnit.Case, async: true import OAuth2.TestHelpers @@ -20,7 +19,8 @@ defmodule OAuth2.Strategy.RefreshTest do assert client.params["grant_type"] == "refresh_token" assert client.params["refresh_token"] == "refresh-token" - assert List.keyfind(client.headers, "authorization", 0) == {"authorization", "Basic #{base64}"} + assert List.keyfind(client.headers, "authorization", 0) == + {"authorization", "Basic #{base64}"} end test "get_token throws and error if there is no 'refresh_token' param" do diff --git a/test/oauth2/util_test.exs b/test/oauth2/util_test.exs index 0a329f7..cb90de7 100644 --- a/test/oauth2/util_test.exs +++ b/test/oauth2/util_test.exs @@ -5,9 +5,15 @@ defmodule OAuth2.UtilTest do test "parses mime types" do assert "application/json" == Util.content_type([]) - assert "application/vnd.api+json" == Util.content_type([{"content-type", "application/vnd.api+json"}]) - assert "application/xml" == Util.content_type([{"content-type", "application/xml; version=1.0"}]) - assert "application/json" == Util.content_type([{"content-type", "application/json;param;param"}]) + + assert "application/vnd.api+json" == + Util.content_type([{"content-type", "application/vnd.api+json"}]) + + assert "application/xml" == + Util.content_type([{"content-type", "application/xml; version=1.0"}]) + + assert "application/json" == + Util.content_type([{"content-type", "application/json;param;param"}]) assert_raise OAuth2.Error, fn -> Util.content_type([{"content-type", "trash; trash"}]) @@ -16,5 +22,9 @@ defmodule OAuth2.UtilTest do assert_raise OAuth2.Error, fn -> Util.content_type([{"content-type", "trash"}]) end + + assert_raise OAuth2.Error, fn -> + Util.content_type([{"content-type", "trash/trash/trash"}]) + end end end diff --git a/test/oauth2_test.exs b/test/oauth2_test.exs index 99f1188..252ae47 100644 --- a/test/oauth2_test.exs +++ b/test/oauth2_test.exs @@ -4,10 +4,12 @@ defmodule OAuth2Test do doctest OAuth2 - @client build_client(client_id: "abc123", - client_secret: "xyz987", - site: "https://api.github.com", - redirect_uri: "http://localhost/auth/callback") + @client build_client( + client_id: "abc123", + client_secret: "xyz987", + site: "https://api.github.com", + redirect_uri: "http://localhost/auth/callback" + ) test "`new` delegates to `OAuth2.Client.new/1`" do client = @client @@ -24,4 +26,3 @@ defmodule OAuth2Test do assert client.redirect_uri == "http://localhost/auth/callback" end end - diff --git a/test/support/test_helpers.ex b/test/support/test_helpers.ex index 4a04798..6b34b98 100644 --- a/test/support/test_helpers.ex +++ b/test/support/test_helpers.ex @@ -1,5 +1,5 @@ defmodule OAuth2.TestHelpers do - + @moduledoc false import Plug.Conn import ExUnit.Assertions @@ -10,11 +10,12 @@ defmodule OAuth2.TestHelpers do def bypass(server, method, path, fun) do bypass(server, method, path, [], fun) end + def bypass(server, method, path, opts, fun) do - {token, opts} = Keyword.pop(opts, :token, nil) + {token, opts} = Keyword.pop(opts, :token, nil) {accept, _opts} = Keyword.pop(opts, :accept, "json") - Bypass.expect server, fn conn -> + Bypass.expect(server, fn conn -> conn = parse_req_body(conn) assert conn.method == method @@ -23,18 +24,16 @@ defmodule OAuth2.TestHelpers do assert_token(conn, token) fun.(conn) - end + end) end def unix_now do - {mega, sec, _micro} = :os.timestamp - (mega * 1_000_000) + sec + {mega, sec, _micro} = :os.timestamp() + mega * 1_000_000 + sec end defp parse_req_body(conn) do - opts = [parsers: [:urlencoded, :json], - pass: ["*/*"], - json_decoder: Jason] + opts = [parsers: [:urlencoded, :json], pass: ["*/*"], json_decoder: Jason] Plug.Parsers.call(conn, Plug.Parsers.init(opts)) end @@ -42,12 +41,14 @@ defmodule OAuth2.TestHelpers do mime = case accept do "json" -> "application/json" - _ -> accept + _ -> accept end + assert get_req_header(conn, "accept") == [mime] end defp assert_token(_conn, nil), do: :ok + defp assert_token(conn, token) do assert get_req_header(conn, "authorization") == ["Bearer #{token.access_token}"] end @@ -71,6 +72,7 @@ defmodule OAuth2.TestHelpers do |> Keyword.merge(opts) |> stringify_keys() |> OAuth2.AccessToken.new() + %{client | token: token} end @@ -84,21 +86,21 @@ defmodule OAuth2.TestHelpers do end defp default_client_opts do - [client_id: get_config(:client_id), - client_secret: get_config(:client_secret), - redirect_uri: get_config(:redirect_uri), - request_opts: get_config(:request_opts)] + [ + client_id: get_config(:client_id), + client_secret: get_config(:client_secret), + redirect_uri: get_config(:redirect_uri), + request_opts: get_config(:request_opts) + ] end defp default_token_opts do - [access_token: "abcdefgh", - expires_at: OAuth2.Util.unix_now + 600, - token_type: "Bearer"] + [access_token: "abcdefgh", expires_at: OAuth2.Util.unix_now() + 600, token_type: "Bearer"] end defp stringify_keys(dict) do dict - |> Enum.map(fn {k,v} -> {Atom.to_string(k), v} end) + |> Enum.map(fn {k, v} -> {Atom.to_string(k), v} end) |> Enum.into(%{}) end end