diff --git a/lib/spacesuit/session_service.ex b/lib/spacesuit/session_service.ex index abedacf..366c5c8 100644 --- a/lib/spacesuit/session_service.ex +++ b/lib/spacesuit/session_service.ex @@ -1,36 +1,3 @@ -defmodule SessionService do - @moduledoc """ - Describe a SessionService implementation. Use for handling auth derived - from bearer tokens. - """ - - @callback validate_api_token(String.t()) :: Tuple.t() - @callback handle_bearer_token(Map.t(), Map.t(), String.t(), String.t()) :: Tuple.t() -end - -defmodule Spacesuit.MockSessionService do - @behaviour SessionService - @moduledoc """ - Mock session service used in testing the AuthHandler - """ - - def validate_api_token(token) do - case token do - "ok" -> :ok - "error" -> :error - _ -> :error - end - end - - def handle_bearer_token(req, env, token, _url) do - case token do - "ok" -> {:ok, req, env} - "error" -> {:stop, req} - _ -> {:ok, req, env} - end - end -end - defmodule Spacesuit.SessionService do @moduledoc """ Implementation of a SessionService that calls out to an external service @@ -40,7 +7,8 @@ defmodule Spacesuit.SessionService do require Logger use Elixometer - @behaviour SessionService + @callback validate_api_token(String.t()) :: Tuple.t() + @callback handle_bearer_token(Map.t(), Map.t(), String.t(), String.t()) :: Tuple.t() @http_server Application.get_env(:spacesuit, :http_server) # How many milliseconds before we timeout call to session-service @@ -63,15 +31,13 @@ defmodule Spacesuit.SessionService do {:ok, _, _} -> result - {:error, type, code, error} -> + {:error, type, code, error} when is_binary(error) -> Logger.error("Session-service #{inspect(type)} error: #{inspect(error)}") + @http_server.reply(code, %{}, error, req) + {:stop, req} - if is_binary(error) do - @http_server.reply(code, %{}, error, req) - else - error_reply(req, 503, "Upstream error") - end - + {:error, _type, _code, _error} -> + error_reply(req, 503, "Upstream error") {:stop, req} {:error, type, error} -> @@ -139,7 +105,8 @@ defmodule Spacesuit.SessionService do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> {:ok, body} - {:ok, %HTTPoison.Response{status_code: code, body: body}} when code >= 400 and code <= 499 -> + {:ok, %HTTPoison.Response{status_code: code, body: body}} + when code >= 400 and code <= 499 -> {:error, :http, code, body} {:error, %HTTPoison.Error{reason: reason}} -> diff --git a/mix.exs b/mix.exs index 4266b41..ffd820c 100644 --- a/mix.exs +++ b/mix.exs @@ -24,16 +24,8 @@ defmodule Spacesuit.Mixfile do # Type "mix help compile.app" for more information def application do [ - applications: [ - :logger, - :cowboy, - :hackney, - :crypto, - :jose, - :exometer_newrelic_reporter, - :elixometer - ], - mod: {Spacesuit, []} + mod: {Spacesuit, []}, + extra_applications: [] ] end @@ -62,6 +54,7 @@ defmodule Spacesuit.Mixfile do # Test only {:excoveralls, "~> 0.6", only: :test}, {:mock, "~> 0.1.1", only: :test}, + {:mox, "~> 0.3.2", only: :test}, {:apex, "~>1.1.0"} ] end diff --git a/mix.lock b/mix.lock index a1f22fd..42ce5dd 100644 --- a/mix.lock +++ b/mix.lock @@ -32,12 +32,13 @@ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, "mock": {:hex, :mock, "0.1.3", "657937b03f88fce89b3f7d6becc9f1ec1ac19c71081aeb32117db9bc4d9b3980", [:mix], [{:meck, "~> 0.8.2", [hex: :meck, optional: false]}]}, + "mox": {:hex, :mox, "0.3.2", "3b9b8364fd4f28628139de701d97c636b27a8f925f57a8d5a1b85fbd620dad3a", [:mix], [], "hexpm"}, "netlink": {:git, "git://github.com/Feuerlabs/netlink.git", "aab67b0a16d88e32a2790a3c0a06c37f98ae121e", [ref: "aab67b0"]}, "parse_trans": {:hex, :parse_trans, "2.9.0", "3f5f7b402928fb9fd200c891e635de909045d1efac40ce3f924d3892898f85eb", [:rebar], [{:edown, "> 0.0.0", [hex: :edown, optional: false]}]}, "pobox": {:hex, :pobox, "1.0.2", "45a5bc91e3cf20f9bc0b94494f00fcdccbc333ab2e0856972b7f0f196fc41613", [:rebar3], []}, "poison": {:hex, :poison, "3.0.0", "625ebd64d33ae2e65201c2c14d6c85c27cc8b68f2d0dd37828fde9c6920dd131", [:mix], []}, "rabbit_common": {:git, "git://github.com/jbrisbin/rabbit_common.git", "9c1965273032ffb79ec0ff80b250e5d0b4608aa7", [tag: "rabbitmq-3.3.5"]}, "ranch": {:git, "https://github.com/ninenines/ranch", "40809cd2b257a8a53bcb5588ecaa88cc5381ff5c", [ref: "1.3.0"]}, - "setup": {:hex, :setup, "1.8.4", "738db0685dc1741f45c6a9bf78478e0d5877f3d0876c0b50fd02f0210edb5aa4", [:rebar3], []}, + "setup": {:hex, :setup, "1.8.4", "738db0685dc1741f45c6a9bf78478e0d5877f3d0876c0b50fd02f0210edb5aa4", [:rebar], []}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []}, } diff --git a/test/spacesuit_auth_middleware_test.exs b/test/spacesuit_auth_middleware_test.exs index 0fd0085..d5d3d3c 100644 --- a/test/spacesuit_auth_middleware_test.exs +++ b/test/spacesuit_auth_middleware_test.exs @@ -1,57 +1,103 @@ defmodule SpacesuitAuthMiddlewareTest do use ExUnit.Case + import Mox + doctest Spacesuit.AuthMiddleware setup_all do - token = "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJhY2N0IjoiMSIsImF6cCI6ImthcmwubWF0dGhpYXNAZ29uaXRyby5jb20iLCJkZWxlZ2F0ZSI6IiIsImV4cCI6IjIwMTctMDItMDNUMTU6MDc6MTRaIiwiZmVhdHVyZXMiOlsidGVhbWRvY3MiLCJjb21iaW5lIiwiZXNpZ24iXSwiaWF0IjoiMjAxNy0wMi0wM1QxNDowNzoxNC40MTMyMTg2OTNaIiwianRpIjoiNTU2ZmU1MTgtYTk0Mi00YTQ3LTkyZmMtNWNmNmVkOWY0YWFhIiwicGVybXMiOlsiYWNjb3VudHM6cmVhZCIsImdyb3VwczpyZWFkIiwidXNlcnM6d3JpdGUiXSwic3ViIjoiY3NzcGVyc29uQGdvbml0cm8uY29tIn0.6eWCzu6yHhgzuvUPaNloNl09uUfaN6nqhK1W--TQwtMk29tf5C5SV-hTT2pxnSxe" + token = + "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJhY2N0IjoiMSIsImF6cCI6ImthcmwubWF0dGhpYXNAZ29uaXRyby5jb20iLCJkZWxlZ2F0ZSI6IiIsImV4cCI6IjIwMTctMDItMDNUMTU6MDc6MTRaIiwiZmVhdHVyZXMiOlsidGVhbWRvY3MiLCJjb21iaW5lIiwiZXNpZ24iXSwiaWF0IjoiMjAxNy0wMi0wM1QxNDowNzoxNC40MTMyMTg2OTNaIiwianRpIjoiNTU2ZmU1MTgtYTk0Mi00YTQ3LTkyZmMtNWNmNmVkOWY0YWFhIiwicGVybXMiOlsiYWNjb3VudHM6cmVhZCIsImdyb3VwczpyZWFkIiwidXNlcnM6d3JpdGUiXSwic3ViIjoiY3NzcGVyc29uQGdvbml0cm8uY29tIn0.6eWCzu6yHhgzuvUPaNloNl09uUfaN6nqhK1W--TQwtMk29tf5C5SV-hTT2pxnSxe" {:ok, token: token} end describe "handling non-bearer tokens" do test "passes through OK when there is no auth header" do - assert {:ok, %{}, %{}} = Spacesuit.AuthMiddleware.execute(%{}, %{}) + assert {:ok, %{}, %{}} = Spacesuit.AuthMiddleware.execute(%{}, %{}) end test "'authorization' header is stripped when present" do - req = %{ headers: %{ "authorization" => "sometoken" }} + req = %{headers: %{"authorization" => "sometoken"}} env = %{} - assert {:ok, %{ headers: %{} }, ^env} = Spacesuit.AuthMiddleware.execute(req, env) + assert {:ok, %{headers: %{}}, ^env} = Spacesuit.AuthMiddleware.execute(req, env) end end describe "handling bearer tokens" do - test "with a valid token", state do - req = %{ headers: %{ "authorization" => "Bearer #{state[:token]}" }, pid: self(), streamid: 1, method: "GET" } + test "with a valid token", %{token: token} do + req = %{ + headers: %{"authorization" => "Bearer #{token}"}, + pid: self(), + streamid: 1, + method: "GET" + } + env = %{} - assert {:ok, %{ headers: _headers }, ^env} = Spacesuit.AuthMiddleware.execute(req, env) + stub(MockSessionService, :handle_bearer_token, fn req, env, ^token, _url -> + {:ok, req, env} + end) + + assert {:ok, %{headers: _headers}, ^env} = Spacesuit.AuthMiddleware.execute(req, env) end test "with invalid bearer token and without session service" do - Application.put_env(:spacesuit, :session_service, %{ enabled: false }) - req = %{ headers: %{ "authorization" => "Bearer balloney" }, pid: self(), streamid: 1, method: "GET" } + Application.put_env(:spacesuit, :session_service, %{enabled: false}) + + req = %{ + headers: %{"authorization" => "Bearer balloney"}, + pid: self(), + streamid: 1, + method: "GET" + } + env = %{} + stub(MockSessionService, :handle_bearer_token, fn req, env, _token, _url -> + {:ok, req, env} + end) + # Should just pass through unaffected assert {:ok, ^req, ^env} = Spacesuit.AuthMiddleware.execute(req, env) end test "with an invalid token when session service is enabled" do - Application.put_env(:spacesuit, :session_service, %{ enabled: true, impl: Spacesuit.MockSessionService }) + Application.put_env(:spacesuit, :session_service, %{enabled: true, impl: MockSessionService}) + + bad_token = "imabadtoken" + + req = %{ + headers: %{"authorization" => "Bearer #{bad_token}"}, + pid: self(), + streamid: 1, + method: "GET" + } - req = %{ headers: %{ "authorization" => "Bearer error" }, pid: self(), streamid: 1, method: "GET" } env = %{} + stub(MockSessionService, :handle_bearer_token, fn req, _env, ^bad_token, _url -> + {:stop, req} + end) + # Unrecognized, we pass it on as is assert {:stop, ^req} = Spacesuit.AuthMiddleware.execute(req, env) end - test "with a valid token when session service is enabled" do - Application.put_env(:spacesuit, :session_service, %{ enabled: true, impl: Spacesuit.MockSessionService }) + test "with a valid token when session service is enabled", %{token: token} do + Application.put_env(:spacesuit, :session_service, %{enabled: true, impl: MockSessionService}) + + stub(MockSessionService, :handle_bearer_token, fn req, env, ^token, _url -> + {:ok, req, env} + end) + + req = %{ + headers: %{"authorization" => "Bearer #{token}"}, + pid: self(), + streamid: 1, + method: "GET" + } - req = %{ headers: %{ "authorization" => "Bearer ok" }, pid: self(), streamid: 1, method: "GET" } env = %{} # Unrecognized, we pass it on as is @@ -59,33 +105,63 @@ defmodule SpacesuitAuthMiddlewareTest do end test "with a missing token when session service is enabled" do - Application.put_env(:spacesuit, :session_service, %{ enabled: true, impl: Spacesuit.MockSessionService }) + Application.put_env(:spacesuit, :session_service, %{enabled: true, impl: MockSessionService}) + + empty_token = "" + + stub(MockSessionService, :handle_bearer_token, fn req, env, ^empty_token, _url -> + {:ok, req, env} + end) - req = %{ headers: %{ "authorization" => "Bearer " }, pid: self(), streamid: 1, method: "GET" } + req = %{headers: %{"authorization" => "Bearer "}, pid: self(), streamid: 1, method: "GET"} env = %{} # Unrecognized, we pass it on as is assert {:ok, ^req, ^env} = Spacesuit.AuthMiddleware.execute(req, env) end - test "with a valid token on a bypassed path" do - Application.put_env(:spacesuit, :session_service, %{ enabled: true, impl: Spacesuit.MockSessionService }) - Application.put_env(:handler_opts, :middleware, %{ session_service: :disabled }) + test "with a valid token on a bypassed path", %{token: token} do + Application.put_env(:spacesuit, :session_service, %{enabled: true, impl: MockSessionService}) + + Application.put_env(:handler_opts, :middleware, %{session_service: :disabled}) + + req = %{ + headers: %{"authorization" => "Bearer #{token}"}, + pid: self(), + streamid: 1, + method: "GET" + } - req = %{ headers: %{ "authorization" => "Bearer ok" }, pid: self(), streamid: 1, method: "GET" } env = %{} + stub(MockSessionService, :handle_bearer_token, fn req, env, ^token, _url -> + {:ok, req, env} + end) + # pass it on as is assert {:ok, ^req, ^env} = Spacesuit.AuthMiddleware.execute(req, env) end test "with an invalid token on a bypassed path" do - Application.put_env(:spacesuit, :session_service, %{ enabled: true, impl: Spacesuit.MockSessionService }) - Application.put_env(:handler_opts, :middleware, %{ session_service: :disabled }) + Application.put_env(:spacesuit, :session_service, %{enabled: true, impl: MockSessionService}) + + Application.put_env(:handler_opts, :middleware, %{session_service: :disabled}) + + bad_token = "iamabadtoken" + + req = %{ + headers: %{"authorization" => "Bearer #{bad_token}"}, + pid: self(), + streamid: 1, + method: "GET" + } - req = %{ headers: %{ "authorization" => "Bearer error" }, pid: self(), streamid: 1, method: "GET" } env = %{} + stub(MockSessionService, :handle_bearer_token, fn req, _env, ^bad_token, _url -> + {:stop, req} + end) + # Unrecognized, we pass it on as is assert {:stop, ^req} = Spacesuit.AuthMiddleware.execute(req, env) end diff --git a/test/test_helper.exs b/test/test_helper.exs index 869559e..463870a 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1,3 @@ ExUnit.start() + +Mox.defmock(MockSessionService, for: Spacesuit.SessionService)