zf

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

commit 174aaa38d2f2bfe246c519d1aada62d2d652c0f6
parent 5f51520c3f00c52cdba047cfd403839216d19003
Author: Alberto Lerda <30939098+albertolerda@users.noreply.github.com>
Date:   Mon,  9 Jan 2023 14:00:46 +0100

Merge pull request #41 from interfacerproject/alberto/did

feat: ifacer did creation and resolution
Diffstat:
Mconf/.env.templ | 6++++--
Mconf/runtime.exs | 22++++++++++++++++++++--
Mdocs/configuration-guide.md | 8+++++---
Mpriv/repo/migrations/20211111175352_fill_vf_agent.exs | 4++--
Msrc/zenflows/application.ex | 1+
Asrc/zenflows/did.ex | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/zenflows/httpc.ex | 2++
Msrc/zenflows/restroom.ex | 36++++++++++++++++++++++++------------
Msrc/zenflows/vf/agent.ex | 4++--
Msrc/zenflows/vf/economic_resource/domain.ex | 2+-
Msrc/zenflows/vf/person.ex | 8++++----
Msrc/zenflows/vf/person/domain.ex | 13+++++++++++++
Msrc/zenflows/vf/person/resolv.ex | 4++++
Msrc/zenflows/vf/person/type.ex | 15++++++++++-----
Mtest/help/factory.ex | 2+-
Mtest/vf/agent/type.test.exs | 4++--
Mtest/vf/person/domain.test.exs | 8++++----
Mtest/vf/person/type.test.exs | 8++++----
18 files changed, 246 insertions(+), 44 deletions(-)

diff --git a/conf/.env.templ b/conf/.env.templ @@ -27,8 +27,7 @@ #export DB_PORT=5432 ## restroom -#export ROOM_HOST= -#export ROOM_PORT= +#export ROOM_URI= export ROOM_SALT=@ROOM_SALT ## admin @@ -38,3 +37,6 @@ export ADMIN_KEY=@ADMIN_KEY export GQL_AUTH_CALLS=true export GQL_DEF_PAGE_SIZE=50 export GQL_MAX_PAGE_SIZE=100 + +#export DID_KEYRING +#export DID_URI diff --git a/conf/runtime.exs b/conf/runtime.exs @@ -27,6 +27,15 @@ get_env_int = fn varname, int -> end end +get_env_url = fn varname, default -> + with {:ok, %{scheme: scheme, host: host, port: port}} when not is_nil(host) and scheme in ["http", "https"] + <- URI.new(get_env(varname, default)) do + %{scheme: :"#{scheme}", host: host, port: port} + else + err -> raise err + end +end + # # database # @@ -68,12 +77,21 @@ config :zenflows, Zenflows.DB.Repo, db_conf # # restroom # +room_uri = get_env_url.("ROOM_URI", "http://localhost") config :zenflows, Zenflows.Restroom, - room_host: get_env("ROOM_HOST", "localhost"), - room_port: get_env_int.("ROOM_PORT", 3000), + room_uri: room_uri, room_salt: fetch_env!("ROOM_SALT") # +# did +# +did_keyring = Base.decode64!(get_env("DID_KEYRING", "")) +did_uri = get_env_url.("DID_URI", "https://did.dyne.org") +config :zenflows, Zenflows.DID, + did_uri: did_uri, + did_keyring: if(did_keyring == "", do: nil, else: Jason.decode!(did_keyring)) + +# # admin # admin_key = fetch_env!("ADMIN_KEY") |> Base.decode16!(case: :lower) diff --git a/docs/configuration-guide.md b/docs/configuration-guide.md @@ -36,12 +36,14 @@ also see the [Required Options](#required-options). This option should be used if extended configuration is desired (using the options mention in the link above). -* `ROOM_HOST`: The hostname or IP address of the Restroom instance. Defaults to `localhost`. -* `ROOM_PORT`: The port number of the Restroom instance. It must be an integer - between `0` and `65535`, inclusive. Defaults to `3000`. +* `ROOM_URI`: The URI of the Restroom instance. Defaults to `http://localhost`. * `ROOM_SALT`: The base64-encoded salt to be used with Restroom's keypairoomServer call. +* `DID_URI`: The URI of the DID controller instance. Defaults to `https://did.dyne.org`. +* `DID_KEYRING`: Keyring (identity) of the server, it is not defined + communication with DID controller is disabled. + * `ADMIN_KEY`: A 64-octect long, lowercase-base16-encoded string used for the authenticating calls from the administrators. Can be generated with `openssl rand -hex 64`. It is automatically generated when you run diff --git a/priv/repo/migrations/20211111175352_fill_vf_agent.exs b/priv/repo/migrations/20211111175352_fill_vf_agent.exs @@ -34,7 +34,7 @@ OR AND eddsa_public_key IS NULL AND ethereum_address IS NULL AND reflow_public_key IS NULL - AND schnorr_public_key IS NULL + AND bitcoin_public_key IS NULL ) """ @@ -55,7 +55,7 @@ def change() do add :eddsa_public_key, :text add :ethereum_address, :text add :reflow_public_key, :text - add :schnorr_public_key, :text + add :bitcoin_public_key, :text # organization add :classified_as, {:array, :text} diff --git a/src/zenflows/application.ex b/src/zenflows/application.ex @@ -29,6 +29,7 @@ def start(_type, _args) do Zenflows.DB.Repo, Zenflows.InstVars.Domain, Zenflows.Restroom, + Zenflows.DID, {Plug.Cowboy, scheme: :http, plug: Zenflows.Web.Router, options: [port: 8000]}, ] diff --git a/src/zenflows/did.ex b/src/zenflows/did.ex @@ -0,0 +1,143 @@ +# Zenflows is designed to implement the Valueflows vocabulary, +# written and maintained by srfsh <info@dyne.org>. +# Copyright (C) 2021-2023 Dyne.org foundation <foundation@dyne.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +defmodule Zenflows.DID do +@moduledoc """ +A module to interact with the did controller instances over HTTPS. +""" + +alias Zenflows.VF.Person + +def child_spec(_) do + Supervisor.child_spec( + {Zenflows.HTTPC, + name: __MODULE__, + scheme: scheme(), + host: host(), + port: port(), + }, + id: __MODULE__) +end + +# Execute a Zencode specified by `name` with JSON data `data`. +@spec exec(String.t(), map()) :: {:ok, map()} | {:error, term()} +defp exec(name, post_data) do + Zenflows.Restroom.request(&Zenflows.HTTPC.request(__MODULE__, &1, &2, &3, &4), + "/v1/sandbox/#{name}", post_data) +end + +@spec did_id(Person.t()) :: String.t() +defp did_id(person) do + "did:dyne:ifacer:#{person.eddsa_public_key}" +end + +@spec get_did(Person.t()) :: {:ok, map()} | {:error, term()} +def get_did(person) do + with {:ok, %{status: stat, data: body}} when stat == 200 <- + Zenflows.HTTPC.request(__MODULE__, "GET", + "/dids/#{did_id(person)}") do + case Jason.decode(body) do + {:ok, data} -> {:ok, %{"created" => false, "did" => data}} + {:error, err} -> {:error, err} + end + else + _err -> {:error, "DID not found"} + end +end + +@spec request_new_did(Person.t()) :: {:ok, map()} | {:error, term()} +def request_new_did(person) do + did_request = %{ + "proof" => %{ + "type" => "EcdsaSecp256k1Signature2019", + "proofPurpose" => "assertionMethod" + }, + "@context" => [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/secp256k1-2019/v1", + "https://w3id.org/security/suites/secp256k1-2020/v1", + "https://dyne.github.io/W3C-DID/specs/ReflowBLS12381.json", + %{ + "description" => "https://schema.org/description", + "identifier" => "https://schema.org/identifier" + } + ], + "did_spec" => "ifacer", + "signer_did_spec" => "ifacer.A", + "identity" => "Ifacer user test", + "ifacer_id" => %{"identifier" => person.id}, + "bitcoin_public_key" => person.bitcoin_public_key, + "ecdh_public_key" => person.ecdh_public_key, + "eddsa_public_key" => person.eddsa_public_key, + "ethereum_address" => person.ethereum_address, + "reflow_public_key" => person.reflow_public_key, + "timestamp" => + DateTime.utc_now() |> DateTime.to_unix(:millisecond) |> to_string + } + + with {:ok, did} <- + Zenflows.Restroom.exec("pubkeys-request-signed", + Map.merge(did_request, keyring())), + {:ok, did_signed} <- exec("pubkeys-accept.chain", did) + do + {:ok, %{"created" => true, "did" => did_signed}} + end +end + +@spec claim(Ecto.Repo.t(), %{person: Person.t()}) :: {:ok, map()} | {:error, term()} +def claim(_repo, %{person: person}) do + if keyring() == nil do + {:error, "DID Controller not configured"} + else + case get_did(person) do + {:ok, did} -> {:ok, did} + _ -> request_new_did(person) + end + end +end + +# Return the scheme of did from the configs. +@spec scheme() :: :http | :https +defp scheme() do + Keyword.fetch!(conf(), :did_uri).scheme +end + +# Return the hostname of did from the configs. +@spec host() :: String.t() +defp host() do + Keyword.fetch!(conf(), :did_uri).host +end + +# Return the port of did from the configs. +@spec port() :: non_neg_integer() +defp port() do + Keyword.fetch!(conf(), :did_uri).port +end + +# Return the private keyring of the server from the configs. +@spec keyring() :: nil | map() +defp keyring() do + Keyword.fetch!(conf(), :did_keyring) +end + +# Return the application configurations of this module. +@spec conf() :: Keyword.t() +defp conf() do + Application.fetch_env!(:zenflows, __MODULE__) +end +end diff --git a/src/zenflows/httpc.ex b/src/zenflows/httpc.ex @@ -18,6 +18,8 @@ def start_link(opts) do GenServer.start_link(__MODULE__, {scheme, host, port}, name: name) end +@spec request(term(), term(), term(), term()) + :: {:ok, term()} | {:error, term()} def request(name, method, path, headers \\ [], body \\ nil, max \\ 5) do headers = case :lists.keyfind("user-agent", 1, headers) do diff --git a/src/zenflows/restroom.ex b/src/zenflows/restroom.ex @@ -24,7 +24,7 @@ def child_spec(_) do Supervisor.child_spec( {Zenflows.HTTPC, name: __MODULE__, - scheme: :http, + scheme: scheme(), host: host(), port: port(), }, @@ -77,13 +77,23 @@ end # Execute a Zencode specified by `name` with JSON data `data`. @spec exec(String.t(), map()) :: {:ok, map()} | {:error, term()} -defp exec(name, post_data) do +def exec(name, post_data) do + request(&Zenflows.HTTPC.request(__MODULE__, &1, &2, &3, &4), + name, post_data) +end + +@doc """ +Given the request function (wrapper of Zenflows.HTTPC.request), the path +and the data to post, it makes the request and parse the result. +""" +@spec request(fun(), String.t(), map()) :: {:ok, map()} | {:error, term()} +def request(request_fn, path, post_data) do hdrs = [{"content-type", "application/json"}] with {:ok, post_body} <- Jason.encode(%{data: post_data}), - {:ok, %{status: stat, data: body}} when stat == 200 or stat == 500 <- - request("POST", "/api/#{name}", hdrs, post_body), - {:ok, data} <- Jason.decode(body) do + {:ok, %{status: stat, data: body}} when stat == 200 or stat == 500 <- + request_fn.("POST", "/api/#{path}", hdrs, post_body), + {:ok, data} <- Jason.decode(body) do if stat == 200 do {:ok, data} else @@ -91,32 +101,34 @@ defp exec(name, post_data) do end else {:ok, %{status: stat, data: body}} -> - {:error, "the http call result in non-200 status code #{stat}: #{inspect(body)}"} + {:error, "the http call result in non-200 status code #{stat}: #{inspect(body)}"} other -> other end end -defp request(method, path, headers, body) do - Zenflows.HTTPC.request(__MODULE__, method, path, headers, body) -end - # Return the salt from the configs. @spec salt() :: String.t() defp salt() do Keyword.fetch!(conf(), :room_salt) end +# Return the scheme of restroom from the configs. +@spec scheme() :: :http | :https +defp scheme() do + Keyword.fetch!(conf(), :room_uri).scheme +end + # Return the hostname of restroom from the configs. @spec host() :: String.t() defp host() do - Keyword.fetch!(conf(), :room_host) + Keyword.fetch!(conf(), :room_uri).host end # Return the port of restroom from the configs. @spec port() :: non_neg_integer() defp port() do - Keyword.fetch!(conf(), :room_port) + Keyword.fetch!(conf(), :room_uri).port end # Return the application configurations of this module. diff --git a/src/zenflows/vf/agent.ex b/src/zenflows/vf/agent.ex @@ -40,7 +40,7 @@ alias Zenflows.VF.SpatialThing eddsa_public_key: String.t() | nil, ethereum_address: String.t() | nil, reflow_public_key: String.t() | nil, - schnorr_public_key: String.t() | nil, + bitcoin_public_key: String.t() | nil, # organization classified_as: [String.t()] | nil, @@ -62,7 +62,7 @@ schema "vf_agent" do field :eddsa_public_key, :string field :ethereum_address, :string field :reflow_public_key, :string - field :schnorr_public_key, :string + field :bitcoin_public_key, :string # organization field :classified_as, {:array, :string} diff --git a/src/zenflows/vf/economic_resource/domain.ex b/src/zenflows/vf/economic_resource/domain.ex @@ -301,7 +301,7 @@ def trace_dpp_ee_before_recurse(%EconomicResource{} = item, visited, depth) do trace_dpp_er_before(item, visited, depth) end def trace_dpp_ee_before_recurse(%EconomicEvent{} = item, visited, depth) do - trace_dpp_pr_before(item, MapSet.put(visited, {item.__struct__, item.id}), depth) + trace_dpp_ee_before(item, MapSet.put(visited, {item.__struct__, item.id}), depth) end def trace_dpp_ee_before_recurse(%Process{} = item, visited, depth) do trace_dpp_pr_before(item, MapSet.put(visited, {item.__struct__, item.id}), depth) diff --git a/src/zenflows/vf/person.ex b/src/zenflows/vf/person.ex @@ -37,7 +37,7 @@ alias Zenflows.VF.SpatialThing eddsa_public_key: String.t() | nil, ethereum_address: String.t() | nil, reflow_public_key: String.t() | nil, - schnorr_public_key: String.t() | nil, + bitcoin_public_key: String.t() | nil, } schema "vf_agent" do @@ -52,7 +52,7 @@ schema "vf_agent" do field :eddsa_public_key, :string field :ethereum_address, :string field :reflow_public_key, :string - field :schnorr_public_key, :string + field :bitcoin_public_key, :string timestamps() end @@ -63,7 +63,7 @@ end eddsa_public_key ethereum_address reflow_public_key - schnorr_public_key + bitcoin_public_key ]a # TODO: Maybe add email to @update_cast as well? # TODO: Maybe add the pubkeys to @update_cast as well? @@ -85,7 +85,7 @@ def changeset(params) do |> Validate.key(:eddsa_public_key) |> Validate.key(:ethereum_address) |> Validate.key(:reflow_public_key) - |> Validate.key(:schnorr_public_key) + |> Validate.key(:bitcoin_public_key) |> Validate.email(:email) |> Changeset.unique_constraint(:user) |> Changeset.unique_constraint(:name) diff --git a/src/zenflows/vf/person/domain.ex b/src/zenflows/vf/person/domain.ex @@ -119,6 +119,19 @@ def delete(id) do end end +@spec claim(Schema.id()) :: + {:ok, Person.t()} | {:error, String.t() | Changeset.t()} +def claim(id) do + Multi.new() + |> multi_one(id) + |> Multi.run(:claim_id, &Zenflows.DID.claim/2) + |> Repo.transaction() + |> case do + {:ok, %{claim_id: value}} -> {:ok, value} + {:error, _, reason, _} -> {:error, reason} + end +end + @spec delete!(Schema.id()) :: Person.t() def delete!(id) do {:ok, value} = delete(id) diff --git a/src/zenflows/vf/person/resolv.ex b/src/zenflows/vf/person/resolv.ex @@ -66,6 +66,10 @@ def delete_person(%{id: id}, _) do end end +def claim_person(%{id: id}, _) do + Domain.claim(id) +end + def images(per, _, _) do per = Domain.preload(per, :images) {:ok, per.images} diff --git a/src/zenflows/vf/person/type.ex b/src/zenflows/vf/person/type.ex @@ -40,7 +40,7 @@ who have no physical location. @eddsa_public_key "eddsa public key, encoded by zenroom" @ethereum_address "ethereum address, encoded by zenroom" @reflow_public_key "reflow public key, encoded by zenroom" -@schnorr_public_key "schnorr public key, encoded by zenroom" +@bitcoin_public_key "bitcoin public key, encoded by zenroom" @desc "A natural person." object :person do @@ -79,8 +79,8 @@ object :person do @desc @reflow_public_key field :reflow_public_key, :string - @desc @schnorr_public_key - field :schnorr_public_key, :string + @desc @bitcoin_public_key + field :bitcoin_public_key, :string end @desc "Person eddsa public key" @@ -120,8 +120,8 @@ input_object :person_create_params do @desc @reflow_public_key field :reflow_public_key, :string - @desc @schnorr_public_key - field :schnorr_public_key, :string + @desc @bitcoin_public_key + field :bitcoin_public_key, :string end input_object :person_update_params do @@ -227,5 +227,10 @@ object :mutation_person do arg :id, non_null(:id) resolve &Resolv.delete_person/2 end + + field :claim_person, non_null(:json) do + arg :id, non_null(:id) + resolve &Resolv.claim_person/2 + end end end diff --git a/test/help/factory.ex b/test/help/factory.ex @@ -273,7 +273,7 @@ def build(:person) do eddsa_public_key: Base.encode64("some eddsa_public_key"), ethereum_address: Base.encode64("some ethereum_address"), reflow_public_key: Base.encode64("some reflow_public_key"), - schnorr_public_key: Base.encode64("some schnorr_public_key"), + bitcoin_public_key: Base.encode64("some bitcoin_public_key"), } end diff --git a/test/vf/agent/type.test.exs b/test/vf/agent/type.test.exs @@ -98,7 +98,7 @@ describe "Query agent()" do eddsaPublicKey ethereumAddress reflowPublicKey - schnorrPublicKey + bitcoinPublicKey } } } @@ -115,7 +115,7 @@ describe "Query agent()" do assert data["eddsaPublicKey"] == per.eddsa_public_key assert data["ethereumAddress"] == per.ethereum_address assert data["reflowPublicKey"] == per.reflow_public_key - assert data["schnorrPublicKey"] == per.schnorr_public_key + assert data["bitcoinPublicKey"] == per.bitcoin_public_key end test "as Organization", %{org: org} do diff --git a/test/vf/person/domain.test.exs b/test/vf/person/domain.test.exs @@ -33,7 +33,7 @@ setup do eddsa_public_key: Base.encode64("eddsa_public_key"), ethereum_address: Base.encode64("ethereum_address"), reflow_public_key: Base.encode64("reflow_public_key"), - schnorr_public_key: Base.encode64("schnorr_public_key"), + bitcoin_public_key: Base.encode64("bitcoin_public_key"), }, inserted: Factory.insert!(:person), } @@ -67,7 +67,7 @@ describe "create/1" do assert new.eddsa_public_key == params.eddsa_public_key assert new.ethereum_address == params.ethereum_address assert new.reflow_public_key == params.reflow_public_key - assert new.schnorr_public_key == params.schnorr_public_key + assert new.bitcoin_public_key == params.bitcoin_public_key end test "with bad params: doesn't create a Person" do @@ -87,7 +87,7 @@ describe "update/2" do assert new.eddsa_public_key == old.eddsa_public_key assert new.ethereum_address == old.ethereum_address assert new.reflow_public_key == old.reflow_public_key - assert new.schnorr_public_key == old.schnorr_public_key + assert new.bitcoin_public_key == old.bitcoin_public_key end test "with bad params: doesn't update the Person", %{inserted: old} do @@ -101,7 +101,7 @@ describe "update/2" do assert new.eddsa_public_key == old.eddsa_public_key assert new.ethereum_address == old.ethereum_address assert new.reflow_public_key == old.reflow_public_key - assert new.schnorr_public_key == old.schnorr_public_key + assert new.bitcoin_public_key == old.bitcoin_public_key end end diff --git a/test/vf/person/type.test.exs b/test/vf/person/type.test.exs @@ -30,7 +30,7 @@ setup do "eddsaPublicKey" => Base.encode64("eddsa_public_key"), "ethereumAddress" => Base.encode64("ethereum_address"), "reflowPublicKey" => Base.encode64("reflow_public_key"), - "schnorrPublicKey" => Base.encode64("schnorr_public_key"), + "bitcoinPublicKey" => Base.encode64("bitcoin_public_key"), }, inserted: Factory.insert!(:person), } @@ -48,7 +48,7 @@ fragment person on Person { eddsaPublicKey ethereumAddress reflowPublicKey - schnorrPublicKey + bitcoinPublicKey } """ @@ -73,7 +73,7 @@ describe "Query" do assert data["eddsaPublicKey"] == new.eddsa_public_key assert data["ethereumAddress"] == new.ethereum_address assert data["reflowPublicKey"] == new.reflow_public_key - assert data["schnorrPublicKey"] == new.schnorr_public_key + assert data["bitcoinPublicKey"] == new.bitcoin_public_key end end @@ -139,7 +139,7 @@ describe "Mutation" do assert data["eddsaPublicKey"] == old.eddsa_public_key assert data["ethereumAddress"] == old.ethereum_address assert data["reflowPublicKey"] == old.reflow_public_key - assert data["schnorrPublicKey"] == old.schnorr_public_key + assert data["bitcoinPublicKey"] == old.bitcoin_public_key end test "deletePerson: doesn't delete the person without the admin key", %{inserted: new} do