zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

commit 7b82807deb4488125a563a01afcb74548bb7a9de
parent 38d194e8d9030f560369df57c23d56514bba16fb
Author: srfsh <dev@srf.sh>
Date:   Wed,  6 Jul 2022 22:24:05 +0200

admin: separate functionality into more sensible modules

Diffstat:
Asrc/zenflows/admin.ex | 29+++++++++++++++++++++++++++++
Dsrc/zenflows/admin/resolv.ex | 36------------------------------------
Dsrc/zenflows/admin/type.ex | 44--------------------------------------------
Msrc/zenflows/gql/schema.ex | 3---
Msrc/zenflows/vf/person/resolv.ex | 11+++++++----
Msrc/zenflows/vf/person/type.ex | 14+++++++++++---
Dtest/admin/type.test.exs | 41-----------------------------------------
Mtest/vf/person/type.test.exs | 65++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
8 files changed, 101 insertions(+), 142 deletions(-)

diff --git a/src/zenflows/admin.ex b/src/zenflows/admin.ex @@ -0,0 +1,29 @@ +defmodule Zenflows.Admin do +@moduledoc """ +Functionality to authenticate of admin-related calls. +""" + +def auth(key) do + with {:ok, key_given} <- Base.decode16(key, case: :lower), + key_want = Application.fetch_env!(:zenflows, Zenflows.Admin)[:admin_key], + true <- keys_match?(key_given, key_want) do + :ok + else _ -> + {:error, "you are not authorized"} + end +end + +# TODO: replace with `:crypto.hash_equals/2` when we require OTP 25. +defp keys_match?(left, right) do + byte_size(left) == byte_size(right) and keys_match?(left, right, 0) +end + +defp keys_match?(<<x, left::binary>>, <<y, right::binary>>, acc) do + xorred = Bitwise.bxor(x, y) + keys_match?(left, right, Bitwise.bor(acc, xorred)) +end + +defp keys_match?(<<>>, <<>>, acc) do + acc === 0 +end +end diff --git a/src/zenflows/admin/resolv.ex b/src/zenflows/admin/resolv.ex @@ -1,36 +0,0 @@ -defmodule Zenflows.Admin.Resolv do -@moduledoc "Resolvers of Admin-related queries." - -alias Zenflows.VF.Person - -def create_user(params, _) do - with :ok <- auth_admin(params), - {:ok, per} <- Person.Domain.create(params) do - {:ok, per} - end -end - -defp auth_admin(%{admin_key: key}) do - with {:ok, key_given} <- Base.decode16(key, case: :lower), - key_want = Application.fetch_env!(:zenflows, Zenflows.Admin)[:admin_key], - true <- keys_match?(key_given, key_want) do - :ok - else _ -> - {:error, "you are not authorized"} - end -end - -# TODO: replace with `:crypto.hash_equals/2` when we require OTP 25. -defp keys_match?(left, right) do - byte_size(left) == byte_size(right) and keys_match?(left, right, 0) -end - -defp keys_match?(<<x, left::binary>>, <<y, right::binary>>, acc) do - xorred = Bitwise.bxor(x, y) - keys_match?(left, right, Bitwise.bor(acc, xorred)) -end - -defp keys_match?(<<>>, <<>>, acc) do - acc === 0 -end -end diff --git a/src/zenflows/admin/type.ex b/src/zenflows/admin/type.ex @@ -1,44 +0,0 @@ -defmodule Zenflows.Admin.Type do -@moduledoc """ -Basic authentication implementation to create Person Agents. -""" - -use Absinthe.Schema.Notation - -alias Zenflows.Admin.Resolv - -@admin_key "The configuration-defined key to authenticate admin calls." - -object :mutation_admin do - @desc "Create a Person Agent, a user." - field :create_user, non_null(:person) do - @desc @admin_key - arg :admin_key, non_null(:string) - - @desc "A valid email address of the user. Must be unique." - arg :email, non_null(:string) - - @desc "The username of the user. Must be unique" - arg :user, non_null(:string) - - @desc "The full name/just a label of the user. Isn't unique." - arg :name, non_null(:string) - - @desc "A JSON object encoded using a URL-safe, Base64 encoding." - arg :pubkeys_encoded, non_null(:string), name: "pubkeys" - - resolve &Resolv.create_user/2 - end - - @desc "Import repositories from a softwarepassport instance." - field :import_repos, :string do - @desc @admin_key - arg :admin_key, non_null(:string) - - @desc "The URL where all the repository information is listed." - arg :url, non_null(:string) - - resolve &Resolv.import_repos/2 - end -end -end diff --git a/src/zenflows/gql/schema.ex b/src/zenflows/gql/schema.ex @@ -7,7 +7,6 @@ alias Zenflows.VF import_types Absinthe.Type.Custom import_types Zenflows.GQL.Type -import_types Zenflows.Admin.Type import_types VF.TimeUnit.Type import_types VF.Action.Type @@ -120,8 +119,6 @@ mutation do #import_fields :mutation_proposal #import_fields :mutation_proposed_intent #import_fields :mutation_proposed_to - - import_fields :mutation_admin end @impl true diff --git a/src/zenflows/vf/person/resolv.ex b/src/zenflows/vf/person/resolv.ex @@ -1,14 +1,16 @@ defmodule Zenflows.VF.Person.Resolv do @moduledoc "Resolvers of Persons." +alias Zenflows.Admin alias Zenflows.VF.{Agent, Person, Person.Domain} def person(%{id: id}, _info) do {:ok, Domain.by_id(id)} end -def create_person(%{person: params}, _info) do - with {:ok, per} <- Domain.create(params) do +def create_person(%{admin_key: key, person: params}, _info) do + with :ok <- Admin.auth(key), + {:ok, per} <- Domain.create(params) do {:ok, %{agent: per}} end end @@ -19,8 +21,9 @@ def update_person(%{person: %{id: id} = params}, _info) do end end -def delete_person(%{id: id}, _info) do - with {:ok, _} <- Domain.delete(id) do +def delete_person(%{admin_key: key, id: id}, _info) do + with :ok <- Admin.auth(key), + {:ok, _} <- Domain.delete(id) do {:ok, true} end end diff --git a/src/zenflows/vf/person/type.ex b/src/zenflows/vf/person/type.ex @@ -20,7 +20,7 @@ who have no physical location. @user "Username of the agent. Implies uniqueness." @email "Email address of the agent. Implies uniqueness." @pubkeys """ -A URL-safe, Base64-encoded string of a JSON object. +A URL-safe, lowercase-Base64-encoded string of a JSON object. """ @desc "A natural person." @@ -49,7 +49,7 @@ object :person do field :email, non_null(:string) @desc @pubkeys - field :pubkeys, non_null(:string), resolve: &Resolv.pubkeys/3 + field :pubkeys, :string, resolve: &Resolv.pubkeys/3 end object :person_response do @@ -79,7 +79,7 @@ input_object :person_create_params do field :email, non_null(:string) @desc @pubkeys - field :pubkeys_encoded, non_null(:string), name: "pubkeys" + field :pubkeys_encoded, :string, name: "pubkeys" end input_object :person_update_params do @@ -116,6 +116,10 @@ object :mutation_person do @desc "Registers a new (human) person with the collaboration space." field :create_person, non_null(:person_response) do arg :person, non_null(:person_create_params) + + @desc "The configuration-defined key to authenticate admin calls." + arg :admin_key, non_null(:string) + resolve &Resolv.create_person/2 end @@ -131,6 +135,10 @@ object :mutation_person do """ field :delete_person, non_null(:boolean) do arg :id, non_null(:id) + + @desc "The configuration-defined key to authenticate admin calls." + arg :admin_key, non_null(:string) + resolve &Resolv.delete_person/2 end end diff --git a/test/admin/type.test.exs b/test/admin/type.test.exs @@ -1,41 +0,0 @@ -defmodule ZenflowsTest.Admin.Type do -use ZenflowsTest.Help.AbsinCase, async: true - -setup do - %{ - params: %{ - admin_key: Application.fetch_env!(:zenflows, Zenflows.Admin)[:admin_key] |> Base.encode16(case: :lower), - name: Factory.str("name"), - email: "#{Factory.str("name")}@example.com", - user: Factory.str("user"), - pubkeys_encoded: Base.url_encode64(Jason.encode!(%{foobar: 1, barfoo: 2})), - }, - } -end - -test "createUser()", %{params: params} do - assert %{data: %{"createUser" => data}} = - mutation!(""" - createUser( - adminKey: "#{params.admin_key}" - name: "#{params.name}" - email: "#{params.email}" - user: "#{params.user}" - pubkeys: "#{params.pubkeys_encoded}" - ) { - id - name - user - email - pubkeys - } - """) - - assert {:ok, _} = Zenflows.DB.ID.cast(data["id"]) - assert data["name"] == params.name - assert data["email"] == params.email - assert data["name"] == params.name - assert data["user"] == params.user - assert data["pubkeys"] == params.pubkeys_encoded -end -end diff --git a/test/vf/person/type.test.exs b/test/vf/person/type.test.exs @@ -3,6 +3,7 @@ use ZenflowsTest.Help.AbsinCase, async: true setup do %{ + admin_key: Application.fetch_env!(:zenflows, Zenflows.Admin)[:admin_key] |> Base.encode16(case: :lower), params: %{ name: Factory.uniq("name"), # image @@ -41,17 +42,46 @@ describe "Query" do end describe "Mutation" do - test "createPerson()", %{params: params} do + test "createPerson() doesn't create a person without the admin key", %{params: params} do + assert %{data: nil, errors: [%{message: "you are not authorized", path: ["createPerson"]}]} = + mutation!(""" + createPerson( + adminKey: "fake!!!" + person: { + name: "#{params.name}" + note: "#{params.note}" + primaryLocation: "#{params.primary_location_id}" + user: "#{params.user}" + email: "#{params.email}" + pubkeys: "#{params.pubkeys_encoded}" + } + ) { + agent { + id + name + note + primaryLocation { id } + user + email + } + } + """) + end + + test "createPerson() creates a person with the admin key", %{params: params, admin_key: key} do assert %{data: %{"createPerson" => %{"agent" => data}}} = mutation!(""" - createPerson(person: { - name: "#{params.name}" - note: "#{params.note}" - primaryLocation: "#{params.primary_location_id}" - user: "#{params.user}" - email: "#{params.email}" - pubkeys: "#{params.pubkeys_encoded}" - }) { + createPerson( + adminKey: "#{key}" + person: { + name: "#{params.name}" + note: "#{params.note}" + primaryLocation: "#{params.primary_location_id}" + user: "#{params.user}" + email: "#{params.email}" + pubkeys: "#{params.pubkeys_encoded}" + } + ) { agent { id name @@ -100,10 +130,23 @@ describe "Mutation" do assert data["email"] == per.email end - test "deletePerson()", %{per: per} do + test "deletePerson() doesn't delete the person without the admin key", %{per: per} do + assert %{data: nil, errors: [%{message: "you are not authorized", path: ["deletePerson"]}]} = + mutation!(""" + deletePerson( + adminKey: "fake!!!" + id: "#{per.id}" + ) + """) + end + + test "deletePerson() deletes the person with the admin key", %{per: per, admin_key: key} do assert %{data: %{"deletePerson" => true}} = mutation!(""" - deletePerson(id: "#{per.id}") + deletePerson( + adminKey: "#{key}" + id: "#{per.id}" + ) """) end end