diff --git a/test/support/fixtures/o_auth/o_auth_fixtures.ex b/test/support/fixtures/o_auth/o_auth_fixtures.ex index 66eab5839..2ae6c2a6b 100644 --- a/test/support/fixtures/o_auth/o_auth_fixtures.ex +++ b/test/support/fixtures/o_auth/o_auth_fixtures.ex @@ -19,6 +19,7 @@ defmodule Teiserver.OAuthFixtures do def code_attrs(user_id, app) do now = DateTime.utc_now() + {verifier, challenge, method} = generate_challenge() %{ value: Base.hex_encode32(:crypto.strong_rand_bytes(32)), @@ -27,8 +28,9 @@ defmodule Teiserver.OAuthFixtures do scopes: app.scopes, expires_at: Timex.add(now, Timex.Duration.from_minutes(5)), redirect_uri: hd(app.redirect_uris), - challenge: "TODO", - challenge_method: :plain + challenge: challenge, + challenge_method: method, + _verifier: verifier } end @@ -66,4 +68,17 @@ defmodule Teiserver.OAuthFixtures do def create_credential(attrs) do %Credential{} |> Credential.changeset(attrs) |> Repo.insert!() end + + defp generate_challenge() do + # A-Z,a-z,0-9 and -._~ are authorized, but can't be bothered to cover all + # of that. hex encoding will fit + verifier = Base.hex_encode32(:crypto.strong_rand_bytes(40), padding: false) + + challenge = hash_verifier(verifier) + + {verifier, challenge, "S256"} + end + + def hash_verifier(verifier), + do: Base.url_encode64(:crypto.hash(:sha256, verifier), padding: false) end diff --git a/test/support/o_auth.ex b/test/support/o_auth.ex deleted file mode 100644 index 7620378b4..000000000 --- a/test/support/o_auth.ex +++ /dev/null @@ -1,45 +0,0 @@ -defmodule Teiserver.Test.Support.OAuth do - alias Teiserver.OAuth - - @doc """ - utility to create an OAuth authorization code for the given user and application - """ - @spec create_code( - Teiserver.Account.User.t(), - %{ - id: integer(), - scopes: OAuth.Application.scopes(), - redirect_uri: String.t(), - challenge: String.t(), - challenge_method: String.t() - }, - OAuth.options() - ) :: {:ok, OAuth.Code.t(), map()} - def create_code(user, app, opts \\ []) do - {verifier, challenge, method} = generate_challenge() - - attrs = %{ - id: app.id, - scopes: app.scopes, - redirect_uri: "http://some.host/a/path", - challenge: challenge, - challenge_method: method - } - - {:ok, code} = OAuth.create_code(user, attrs, opts) - {:ok, code, Map.put(attrs, :verifier, verifier)} - end - - defp generate_challenge() do - # A-Z,a-z,0-9 and -._~ are authorized, but can't be bothered to cover all - # of that. hex encoding will fit - verifier = Base.hex_encode32(:crypto.strong_rand_bytes(40), padding: false) - - challenge = hash_verifier(verifier) - - {verifier, challenge, "S256"} - end - - def hash_verifier(verifier), - do: Base.url_encode64(:crypto.hash(:sha256, verifier), padding: false) -end diff --git a/test/teiserver/o_auth/application_query_test.exs b/test/teiserver/o_auth/application_query_test.exs index dc4a64935..2ec1e36c1 100644 --- a/test/teiserver/o_auth/application_query_test.exs +++ b/test/teiserver/o_auth/application_query_test.exs @@ -2,7 +2,7 @@ defmodule Teiserver.OAuth.ApplicationQueryTest do use Teiserver.DataCase alias Teiserver.Repo - alias Teiserver.OAuth.{Application, Code, ApplicationQueries, Token, Credential} + alias Teiserver.OAuth.ApplicationQueries alias Teiserver.OAuthFixtures defp setup_app(_context) do diff --git a/test/teiserver/o_auth/code_test.exs b/test/teiserver/o_auth/code_test.exs index a5f632f91..085f5d376 100644 --- a/test/teiserver/o_auth/code_test.exs +++ b/test/teiserver/o_auth/code_test.exs @@ -1,7 +1,7 @@ defmodule Teiserver.OAuth.CodeTest do use Teiserver.DataCase, async: true alias Teiserver.OAuth - alias Teiserver.Test.Support.OAuth, as: OAuthTest + alias Teiserver.OAuthFixtures setup do user = Teiserver.TeiserverTestLib.new_user() @@ -11,27 +11,28 @@ defmodule Teiserver.OAuth.CodeTest do name: "Testing app", uid: "test_app_uid", owner_id: user.id, - scopes: ["tachyon.lobby"] + scopes: ["tachyon.lobby"], + redirect_uris: ["http://localhost/foo"] }) {:ok, user: user, app: app} end test "can get valid code", %{user: user, app: app} do - assert {:ok, code, _} = OAuthTest.create_code(user, app) + assert {:ok, code, _} = create_code(user, app) assert {:ok, ^code} = OAuth.get_valid_code(code.value) assert {:error, :no_code} = OAuth.get_valid_code(nil) end test "cannot retrieve expired code", %{user: user, app: app} do yesterday = Timex.shift(Timex.now(), days: -1) - assert {:ok, code, _} = OAuthTest.create_code(user, app, now: yesterday) + assert {:ok, code, _} = create_code(user, app, expires_at: yesterday) assert {:error, :expired} = OAuth.get_valid_code(code.value) end test "can exchange valid code for token", %{user: user, app: app} do - assert {:ok, code, attrs} = OAuthTest.create_code(user, app) - assert {:ok, token} = OAuth.exchange_code(code, attrs.verifier, attrs.redirect_uri) + assert {:ok, code, attrs} = create_code(user, app) + assert {:ok, token} = OAuth.exchange_code(code, attrs._verifier, attrs.redirect_uri) assert token.scopes == code.scopes assert token.owner_id == user.id # the code is now consumed and not available anymore @@ -40,23 +41,36 @@ defmodule Teiserver.OAuth.CodeTest do test "cannot exchange expired code for token", %{user: user, app: app} do yesterday = Timex.shift(Timex.now(), days: -1) - assert {:ok, code, attrs} = OAuthTest.create_code(user, app, now: yesterday) - assert {:error, :expired} = OAuth.exchange_code(code, attrs.verifier) + assert {:ok, code, attrs} = create_code(user, app, expires_at: yesterday) + assert {:error, :expired} = OAuth.exchange_code(code, attrs._verifier) end test "must use valid verifier", %{user: user, app: app} do - assert {:ok, code, attrs} = OAuthTest.create_code(user, app) + assert {:ok, code, attrs} = create_code(user, app) + attrs = Map.put(attrs, :id, app.id) no_match = Base.hex_encode32(:crypto.strong_rand_bytes(38), padding: false) assert {:error, _} = OAuth.exchange_code(code, no_match) verifier = "TOO_SHORT" - challenge = OAuthTest.hash_verifier(verifier) + challenge = OAuthFixtures.hash_verifier(verifier) {:ok, code} = OAuth.create_code(user, %{attrs | challenge: challenge}) assert {:error, _} = OAuth.exchange_code(code, verifier) verifier = String.duplicate("a", 129) - challenge = OAuthTest.hash_verifier(verifier) + challenge = OAuthFixtures.hash_verifier(verifier) {:ok, code} = OAuth.create_code(user, %{attrs | challenge: challenge}) assert {:error, _} = OAuth.exchange_code(code, verifier) end + + defp create_code(user, app, opts \\ []) do + expires_at = + Keyword.get(opts, :expires_at, Timex.add(DateTime.utc_now(), Timex.Duration.from_days(1))) + + attrs = + OAuthFixtures.code_attrs(user.id, app) + |> Map.put(:expires_at, expires_at) + + code = OAuthFixtures.create_code(attrs) + {:ok, code, attrs} + end end diff --git a/test/teiserver_web/controllers/o_auth/code_controller_test.exs b/test/teiserver_web/controllers/o_auth/code_controller_test.exs index 80ba58ef9..a174b73de 100644 --- a/test/teiserver_web/controllers/o_auth/code_controller_test.exs +++ b/test/teiserver_web/controllers/o_auth/code_controller_test.exs @@ -2,7 +2,7 @@ defmodule TeiserverWeb.OAuth.CodeControllerTest do use TeiserverWeb.ConnCase alias Central.Helpers.GeneralTestLib alias Teiserver.OAuth - alias Teiserver.Test.Support.OAuth, as: OAuthTest + alias Teiserver.OAuthFixtures defp get_valid_data(%{app: app, code: code, code_attrs: code_attrs}) do %{ @@ -10,7 +10,7 @@ defmodule TeiserverWeb.OAuth.CodeControllerTest do code: code.value, redirect_uri: code.redirect_uri, client_id: app.uid, - code_verifier: code_attrs.verifier + code_verifier: code_attrs._verifier } end @@ -34,7 +34,8 @@ defmodule TeiserverWeb.OAuth.CodeControllerTest do end defp setup_code(context) do - {:ok, code, attrs} = OAuthTest.create_code(context[:user], context[:app]) + attrs = OAuthFixtures.code_attrs(context[:user].id, context[:app]) + code = OAuthFixtures.create_code(attrs) %{code: code, code_attrs: attrs} end