diff --git a/.gitignore b/.gitignore index ab8c059e5b..3ba1681b55 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ data # MacOS .DS_Store + +# ignore generated pem files from running the nuts-node executable. +/*.pem diff --git a/README.rst b/README.rst index 0c40cd645c..35d0dcf025 100644 --- a/README.rst +++ b/README.rst @@ -42,7 +42,7 @@ The server and client API is generated from the open-api spec: .. code-block:: shell - oapi-codegen -generate types,server,client -package api docs/_static/example.yaml > api/generated.go + make gen-api Generating Mocks **************** @@ -51,8 +51,7 @@ These mocks are used by other modules .. code-block:: shell - mockgen -destination=mock/mock_example.go -package=mock -source=example.go - + make gen-mocks README ****** @@ -60,7 +59,7 @@ The readme is auto-generated from a template and uses the documentation to fill .. code-block:: shell - ./generate_readme.sh + make gen-readme This script uses ``rst_include`` which is installed as part of the dependencies for generating the documentation. @@ -74,11 +73,54 @@ The documentation can be build by running /docs $ make html -The resulting html will be available from ``docs/_build/html/index.html`` +Requirements for running sphinx +=============================== + + - install python3 + - install pip3 (if it doesn't install automatically) + - ``pip3 install sphinx`` + - ``pip3 install recommonmark`` + - ``pip3 install sphinx_rtd_theme`` + - ``pip3 install rst_include`` + - ``pip3 install sphinx-jsonschema`` + - ``pip3 install sphinxcontrib-httpdomain`` Configuration ************* -config stuff -============ +The Nuts-go library contains some configuration logic which allows for usage of configFiles, Environment variables and commandLine params transparently. +If a Nuts engine is added as Engine it'll automatically work for the given engine. It is also possible for an engine to add the capabilities on a standalone basis. +This allows for testing from within a repo. + +The parameters follow the following convention: +``$ nuts --parameter X`` is equal to ``$ NUTS_PARAMETER=X nuts`` is equal to ``parameter: X`` in a yaml file. + +Or for this piece of yaml + +.. code-block:: yaml + + nested: + parameter: X + +is equal to ``$ nuts --nested.parameter X`` is equal to ``$ NUTS_NESTED_PARAMETER=X nuts`` + +Config parameters for engines are prepended by the ``engine.ConfigKey`` by default (configurable): + +.. code-block:: yaml + + engine: + nested: + parameter: X + +is equal to ``$ nuts --engine.nested.parameter X`` is equal to ``$ NUTS_ENGINE_NESTED_PARAMETER=X nuts`` + + +Options +******* + +The following options can be configured: + +.. marker-for-config-options + +.. include:: options.rst diff --git a/README_template.rst b/README_template.rst index 9aabaddf15..aea2ca9de4 100644 --- a/README_template.rst +++ b/README_template.rst @@ -15,7 +15,7 @@ Distributed registry for storing and querying health care providers their vendor :target: https://codecov.io/gh/nuts-foundation/nuts-node :alt: Code coverage -.. image:: https://api.codeclimate.com/v1/badges/040468237c838c03ff7d/maintainability +.. image:: https://api.codeclimate.com/v1/badges/69f77bd34f3ac253cae0/maintainability :target: https://codeclimate.com/github/nuts-foundation/nuts-node/maintainability :alt: Maintainability @@ -25,5 +25,5 @@ Distributed registry for storing and querying health care providers their vendor Configuration ************* -.. include:: docs/pages/configuration.rst +.. include:: docs/pages/configuration/configuration.rst :start-after: .. marker-for-readme diff --git a/cmd/root.go b/cmd/root.go index 001e00e06f..ba2fa34b27 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,6 +1,6 @@ /* - * Nuts go - * Copyright (C) 2019 Nuts community + * Nuts node + * Copyright (C) 2021 Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,8 @@ import ( "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/nuts-foundation/nuts-node/core" + crypto "github.com/nuts-foundation/nuts-node/crypto/engine" + vdr "github.com/nuts-foundation/nuts-node/vdr/engine" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -102,6 +104,9 @@ func registerEngines() { core.RegisterEngine(core.NewStatusEngine()) core.RegisterEngine(core.NewLoggerEngine()) core.RegisterEngine(core.NewMetricsEngine()) + core.RegisterEngine(crypto.NewCryptoEngine()) + core.RegisterEngine(vdr.NewVDREngine()) + } func injectConfig(cfg *core.NutsGlobalConfig) { @@ -116,7 +121,7 @@ func injectConfig(cfg *core.NutsGlobalConfig) { func configureEngines() { for _, e := range core.EngineCtl.Engines { // only if Engine is dynamically configurable - if e.Configure != nil { + if e.Configurable != nil { if err := e.Configure(); err != nil { logrus.Fatal(err) } @@ -132,7 +137,7 @@ func addFlagSets(cmd *cobra.Command, cfg *core.NutsGlobalConfig) { func startEngines() { for _, e := range core.EngineCtl.Engines { - if e.Start != nil { + if e.Runnable != nil { if err := e.Start(); err != nil { logrus.Fatal(err) } @@ -142,7 +147,7 @@ func startEngines() { func shutdownEngines() { for _, e := range core.EngineCtl.Engines { - if e.Shutdown != nil { + if e.Runnable != nil { if err := e.Shutdown(); err != nil { logrus.Error(err) } diff --git a/codecov.yml b/codecov.yml index 3a5445a2a9..569e94c062 100644 --- a/codecov.yml +++ b/codecov.yml @@ -5,3 +5,5 @@ ignore: - "mock/*" - "**/mock.go" - "docs/*" +github_checks: + annotations: false diff --git a/core/config.go b/core/config.go index d8e3c8f5ce..f3d2a0ab27 100644 --- a/core/config.go +++ b/core/config.go @@ -1,6 +1,6 @@ /* - * Nuts go - * Copyright (C) 2019 Nuts community + * Nuts node + * Copyright (C) 2021 Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/core/config_test.go b/core/config_test.go index c33eccd210..c9d73790de 100644 --- a/core/config_test.go +++ b/core/config_test.go @@ -1,6 +1,6 @@ /* - * Nuts go - * Copyright (C) 2019 Nuts community + * Nuts node + * Copyright (C) 2021 Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/core/constants.go b/core/constants.go index 8e6ed0c9a3..2eb258b2bc 100644 --- a/core/constants.go +++ b/core/constants.go @@ -1,6 +1,6 @@ /* - * Nuts go core - * Copyright (C) 2019 Nuts community + * Nuts node + * Copyright (C) 2021 Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/core/diagnostics.go b/core/diagnostics.go index eb1e417dca..eee628d35f 100644 --- a/core/diagnostics.go +++ b/core/diagnostics.go @@ -1,6 +1,6 @@ /* - * Nuts go core - * Copyright (C) 2019 Nuts community + * Nuts node + * Copyright (C) 2021 Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,11 @@ package core +// Diagnosable allows the implementer, mostly engines, to return diagnostics. +type Diagnosable interface { + Diagnostics() []DiagnosticResult +} + // DiagnosticResult are the result of different checks giving information on how well the system is doing type DiagnosticResult interface { // Name returns a simple and understandable name of the check diff --git a/core/diagnostics_test.go b/core/diagnostics_test.go index e2dddb040e..2ec83483a7 100644 --- a/core/diagnostics_test.go +++ b/core/diagnostics_test.go @@ -1,6 +1,6 @@ /* - * Nuts go core - * Copyright (C) 2019 Nuts community + * Nuts node + * Copyright (C) 2021 Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/core/engine.go b/core/engine.go index 32f92562c1..041a3cb2e5 100644 --- a/core/engine.go +++ b/core/engine.go @@ -1,6 +1,6 @@ /* - * Nuts go - * Copyright (C) 2019 Nuts community + * Nuts node + * Copyright (C) 2021 Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -50,6 +50,21 @@ type EchoRouter interface { // START_DOC_ENGINE_1 +// Runnable is the interface that groups the Start and Shutdown methods. +// When an engine implements these they will be called on startup and shutdown. +// Start and Shutdown should not be called more than once +type Runnable interface { + Start() error + Shutdown() error +} + +// Configurable is the interface that contains the Configure method. +// When an engine implements the Configurable interface, it will be called before startup. +// Configure should only be called once per engine instance +type Configurable interface { + Configure() error +} + // Engine contains all the configuration options and callbacks needed by the executable to configure, start, monitor and shutdown the engines type Engine struct { // Name holds the human readable name of the engine @@ -70,23 +85,15 @@ type Engine struct { // Config is the pointer to a config struct. The config will be unmarshalled using the ConfigKey. Config interface{} - // Configure checks if the combination of config parameters is allowed - Configure func() error - - // Diagnostics returns a slice of DiagnosticResult - Diagnostics func() []DiagnosticResult + Diagnosable + Runnable + Configurable // FlasSet contains all engine-local configuration possibilities so they can be displayed through the help command FlagSet *pflag.FlagSet // Routes passes the Echo router to the specific engine for it to register their routes. Routes func(router EchoRouter) - - // Shutdown the engine - Shutdown func() error - - // Start the engine, this will spawn any clients, background tasks or active processes. - Start func() error } // END_DOC_ENGINE_1 diff --git a/core/engine_test.go b/core/engine_test.go index 618d52bfb2..78a24d38b9 100644 --- a/core/engine_test.go +++ b/core/engine_test.go @@ -1,6 +1,6 @@ /* - * Nuts go - * Copyright (C) 2019 Nuts community + * Nuts node + * Copyright (C) 2021 Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/core/logging.go b/core/logging.go index a751903d3a..4e4045f0b6 100644 --- a/core/logging.go +++ b/core/logging.go @@ -1,6 +1,6 @@ /* - * Nuts go - * Copyright (C) 2019 Nuts community + * Nuts node + * Copyright (C) 2021 Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,9 +42,16 @@ func NewLoggerEngine() *Engine { log.Infof("Verbosity is set to %s\n", lc.verbosity) }, }, - Diagnostics: func() []DiagnosticResult { - dr := &GenericDiagnosticResult{Title: "verbosity", Outcome: lc.verbosity} - return []DiagnosticResult{dr} - }, + Diagnosable: loggerEngine{config: &lc}, } } + +type loggerEngine struct { + config *loggerConfig +} + +// Diagnostics returns the diagnostics of the LoggerEngine. +func (l loggerEngine) Diagnostics() []DiagnosticResult { + dr := &GenericDiagnosticResult{Title: "verbosity", Outcome: l.config.verbosity} + return []DiagnosticResult{dr} +} diff --git a/core/metrics.go b/core/metrics.go index 5ce62697aa..0ad8c592d3 100644 --- a/core/metrics.go +++ b/core/metrics.go @@ -1,6 +1,6 @@ /* - * Nuts go core - * Copyright (C) 2020 Nuts community + * Nuts node + * Copyright (C) 2021 Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,21 +25,23 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" ) -const NutsMetricsPrefix = "nuts_" - // NewMetricsEngine creates a new Engine for exposing prometheus metrics via http. // Metrics are exposed on /metrics, by default the GoCollector and ProcessCollector are enabled. func NewMetricsEngine() *Engine { return &Engine{ - Name: "Metrics", - Configure: configure, + Name: "Metrics", + Configurable: metricsEngine{}, Routes: func(router EchoRouter) { router.GET("/metrics", echo.WrapHandler(promhttp.Handler())) }, } } -func configure() error { +type metricsEngine struct{} + +// Configure configures the MetricsEngine. +// It configures and registers the prometheus collector +func (metricsEngine) Configure() error { collectors := []prometheus.Collector{ prometheus.NewGoCollector(), prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}), diff --git a/core/metrics_test.go b/core/metrics_test.go index 8664f8d03b..92e273b9b3 100644 --- a/core/metrics_test.go +++ b/core/metrics_test.go @@ -1,6 +1,6 @@ /* - * Nuts go core - * Copyright (C) 2020 Nuts community + * Nuts node + * Copyright (C) 2021 Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/core/status.go b/core/status.go index 6a5c6102f0..53e53475c6 100644 --- a/core/status.go +++ b/core/status.go @@ -1,6 +1,6 @@ /* - * Nuts go - * Copyright (C) 2019 Nuts community + * Nuts node + * Copyright (C) 2021 Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,9 +39,7 @@ func NewStatusEngine() *Engine { diagnosticsSummaryAsText() }, }, - Diagnostics: func() []DiagnosticResult { - return []DiagnosticResult{diagnostics()} - }, + Diagnosable: status{}, Routes: func(router EchoRouter) { router.GET("/status/diagnostics", diagnosticsOverview) router.GET("/status", StatusOK) @@ -56,7 +54,7 @@ func diagnosticsOverview(ctx echo.Context) error { func diagnosticsSummaryAsText() string { var lines []string for _, e := range EngineCtl.Engines { - if e.Diagnostics != nil { + if e.Diagnosable != nil { lines = append(lines, e.Name) diagnostics := e.Diagnostics() for _, d := range diagnostics { @@ -68,8 +66,12 @@ func diagnosticsSummaryAsText() string { return strings.Join(lines, "\n") } -func diagnostics() DiagnosticResult { - return &GenericDiagnosticResult{Title: "Registered engines", Outcome: strings.Join(listAllEngines(), ",")} +type status struct{} + +// Diagnostics returns list of DiagnosticResult for the StatusEngine. +// The results are a list of all registered engines +func (status) Diagnostics() []DiagnosticResult { + return []DiagnosticResult{&GenericDiagnosticResult{Title: "Registered engines", Outcome: strings.Join(listAllEngines(), ",")}} } // StatusOK returns 200 OK with a "OK" body diff --git a/crypto/api/v1/api.go b/crypto/api/v1/api.go index 0ccc2714ee..230c5e920d 100644 --- a/crypto/api/v1/api.go +++ b/crypto/api/v1/api.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2019. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/crypto/api/v1/api_test.go b/crypto/api/v1/api_test.go index c8fe0ab8e9..425f7d09c0 100644 --- a/crypto/api/v1/api_test.go +++ b/crypto/api/v1/api_test.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2019. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,7 +25,7 @@ import ( "testing" "github.com/golang/mock/gomock" - mock2 "github.com/nuts-foundation/nuts-node/crypto/mock" + mock2 "github.com/nuts-foundation/nuts-node/crypto" "github.com/nuts-foundation/nuts-node/crypto/storage" "github.com/nuts-foundation/nuts-node/crypto/test" "github.com/nuts-foundation/nuts-node/mock" diff --git a/crypto/api/v1/client.go b/crypto/api/v1/client.go index f711838c91..9bb2f9642f 100644 --- a/crypto/api/v1/client.go +++ b/crypto/api/v1/client.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2020. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/crypto/api/v1/client_test.go b/crypto/api/v1/client_test.go index 9d03ace1ee..61bd978e95 100644 --- a/crypto/api/v1/client_test.go +++ b/crypto/api/v1/client_test.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2020. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/crypto/api/v1/generated.go b/crypto/api/v1/generated.go index e39292278a..cee6a0d18a 100644 --- a/crypto/api/v1/generated.go +++ b/crypto/api/v1/generated.go @@ -463,3 +463,4 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/internal/crypto/v1/sign_jwt", wrapper.SignJwt) } + diff --git a/crypto/api/v1/generated_test.go b/crypto/api/v1/generated_test.go index b353a55257..eea5f30dd8 100644 --- a/crypto/api/v1/generated_test.go +++ b/crypto/api/v1/generated_test.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2019. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/crypto/crypto.go b/crypto/crypto.go index 580843174c..87b7ae6f78 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2019. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -82,6 +82,12 @@ func (client *Crypto) Shutdown() error { return nil } +// Start starts the crypto engine. +func (client *Crypto) Start() error { + // currently empty but here to make crypto implement the Runnable interface. + return nil +} + // GetPrivateKey returns the specified private key. It can be used for signing, but cannot be exported. func (client *Crypto) GetPrivateKey(kid string) (crypto.Signer, error) { priv, err := client.Storage.GetPrivateKey(kid) @@ -135,13 +141,16 @@ func (client *Crypto) doConfigure() error { // New generates a new key pair. If a key is overwritten is handled by the storage implementation. // it's considered bad practise to reuse a kid for different keys. -func (client *Crypto) New(namingFunc KidNamingFunc) (crypto.PublicKey, string, error) { +func (client *Crypto) New(namingFunc KIDNamingFunc) (crypto.PublicKey, string, error) { keyPair, err := generateECKeyPair() if err != nil { return nil, "", err } - kid := namingFunc(keyPair.Public()) + kid, err := namingFunc(keyPair.Public()) + if err != nil { + return nil, "", err + } if err = client.Storage.SavePrivateKey(kid, keyPair); err != nil { return nil, "", err } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 58d9508131..629313141d 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2019. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -138,6 +138,14 @@ func TestCrypto_New(t *testing.T) { assert.NotNil(t, publicKey) assert.Equal(t, kid, returnKid) }) + + t.Run("error - NamingFunction returns err", func(t *testing.T) { + errorNamingFunc := func(key crypto.PublicKey) (string, error) { + return "", errors.New("b00m!") + } + _, _, err := client.New(errorNamingFunc) + assert.Error(t, err) + }) } func TestCrypto_doConfigure(t *testing.T) { diff --git a/crypto/engine/engine.go b/crypto/engine/engine.go index e7c54010c7..25823282e0 100644 --- a/crypto/engine/engine.go +++ b/crypto/engine/engine.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2019. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -44,16 +44,16 @@ func NewCryptoEngine() *core.Engine { cb := crypto2.Instance() return &core.Engine{ - Cmd: cmd(), - Config: &cb.Config, - ConfigKey: "crypto", - Configure: cb.Configure, - FlagSet: flagSet(), - Name: "Crypto", + Cmd: cmd(), + Config: &cb.Config, + ConfigKey: "crypto", + Configurable: cb, + FlagSet: flagSet(), + Name: "Crypto", Routes: func(router core.EchoRouter) { api.RegisterHandlers(router, &api.Wrapper{C: cb}) }, - Shutdown: cb.Shutdown, + Runnable: cb, } } diff --git a/crypto/engine/engine_test.go b/crypto/engine/engine_test.go index f27db3e97f..16e374bde9 100644 --- a/crypto/engine/engine_test.go +++ b/crypto/engine/engine_test.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2019. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -73,6 +73,7 @@ func (h handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { writer.WriteHeader(h.statusCode) writer.Write(h.responseData) } + var jwkAsString = ` { "kty" : "RSA", diff --git a/crypto/hash/sha256.go b/crypto/hash/sha256.go new file mode 100644 index 0000000000..296c919ee2 --- /dev/null +++ b/crypto/hash/sha256.go @@ -0,0 +1,125 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package hash + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" +) + +// SHA256HashSize holds the size of a sha256 hash in bytes. +const SHA256HashSize = 32 + +// SHA256Hash is a SHA256 Hash over some bytes +type SHA256Hash [SHA256HashSize]byte + +// SHA256Sum creates a sha256 hash from the given bytes +func SHA256Sum(data []byte) SHA256Hash { + return sha256.Sum256(data) +} + +// String returns the SHA256Hash as a hexadecimal string. +func (h SHA256Hash) String() string { + return hex.EncodeToString(h[:]) +} + +// EmptyHash returns a Hash that is empty (initialized with zeros). +func EmptyHash() SHA256Hash { + return [SHA256HashSize]byte{} +} + +// Empty tests whether the Hash is empty (all zeros). +func (h SHA256Hash) Empty() bool { + for _, b := range h { + if b != 0 { + return false + } + } + return true +} + +// Clone returns a copy of the Hash. +func (h SHA256Hash) Clone() SHA256Hash { + clone := EmptyHash() + copy(clone[:], h[:]) + return clone +} + +// Slice returns the Hash as a slice. It does not copy the array. +func (h SHA256Hash) Slice() []byte { + return h[:] +} + +// Equals determines whether the given Hash is exactly the same (bytes match). +func (h SHA256Hash) Equals(other SHA256Hash) bool { + return h.Compare(other) == 0 +} + +// Compare compares this Hash to another Hash using bytes.Compare. +func (h SHA256Hash) Compare(other SHA256Hash) int { + return bytes.Compare(h[:], other[:]) +} + +// MarshalJSON marshals the hash as hex-encoded string +func (h SHA256Hash) MarshalJSON() ([]byte, error) { + s := h.String() + return json.Marshal(s) +} + +// UnmarshalJSON converts from hex-encoded json value +func (h *SHA256Hash) UnmarshalJSON(data []byte) error { + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return err + } + + hash, err := ParseHex(s) + if err != nil { + return err + } + copy(h[:], hash[:]) + + return nil +} + +// FromSlice converts a byte slice to a Hash, returning a copy. +func FromSlice(slice []byte) SHA256Hash { + result := EmptyHash() + copy(result[:], slice) + return result +} + +// ParseHex parses the given input string as Hash. If the input is invalid and can't be parsed as Hash, an error is returned. +func ParseHex(input string) (SHA256Hash, error) { + if input == "" { + return EmptyHash(), nil + } + data, err := hex.DecodeString(input) + if err != nil { + return EmptyHash(), err + } + if len(data) != SHA256HashSize { + return EmptyHash(), fmt.Errorf("incorrect hash length (%d)", len(data)) + } + result := EmptyHash() + copy(result[0:], data) + return result, nil +} diff --git a/crypto/hash/sha256_test.go b/crypto/hash/sha256_test.go new file mode 100644 index 0000000000..44f27be8e0 --- /dev/null +++ b/crypto/hash/sha256_test.go @@ -0,0 +1,182 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package hash + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHash_Empty(t *testing.T) { + t.Run("empty", func(t *testing.T) { + assert.True(t, EmptyHash().Empty()) + }) + t.Run("non empty", func(t *testing.T) { + h, err := ParseHex("452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620") + if !assert.NoError(t, err) { + return + } + assert.False(t, h.Empty()) + }) + t.Run("returns new hash every time", func(t *testing.T) { + h1 := EmptyHash() + h2 := EmptyHash() + assert.True(t, h1.Equals(h2)) + h1[0] = 10 + assert.False(t, h1.Equals(h2)) + }) +} + +func TestHash_Slice(t *testing.T) { + t.Run("slice parsed hash", func(t *testing.T) { + h, err := ParseHex("452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620") + if !assert.NoError(t, err) { + return + } + assert.Equal(t, h, FromSlice(h.Slice())) + }) + t.Run("returns new slice every time", func(t *testing.T) { + h, _ := ParseHex("452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620") + s1 := h.Slice() + s2 := h.Slice() + assert.Equal(t, s1, s2) + s1[0] = 10 + assert.NotEqual(t, s1, s2) + }) +} + +func TestParseHex(t *testing.T) { + t.Run("ok", func(t *testing.T) { + hash, err := ParseHex("452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620") + assert.NoError(t, err) + assert.Equal(t, "452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620", hash.String()) + }) + t.Run("ok - empty string input", func(t *testing.T) { + hash, err := ParseHex("") + assert.NoError(t, err) + assert.True(t, hash.Empty()) + }) + t.Run("error - invalid input", func(t *testing.T) { + hash, err := ParseHex("a23da") + assert.Error(t, err) + assert.True(t, hash.Empty()) + }) + t.Run("error - incorrect length", func(t *testing.T) { + hash, err := ParseHex("383c9da631bd120169e82b0679e4c2e8d5050894383c9da631bd120169e82b0679e4c2e8d5050894") + assert.EqualError(t, err, "incorrect hash length (40)") + assert.True(t, hash.Empty()) + }) +} + +func TestSHA256Hash(t *testing.T) { + s := "hi" + h := SHA256Sum([]byte(s)) + + assert.Equal(t, "8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4", h.String()) +} + +func TestSHA256Hash_Clone(t *testing.T) { + s := "hi" + h := SHA256Sum([]byte(s)) + c := h.Clone() + + assert.Equal(t, "8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4", c.String()) +} + +func TestSHA256Hash_MarshalJSON(t *testing.T) { + s := "452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620" + h, _ := ParseHex(s) + + t.Run("ok", func(t *testing.T) { + bytes, err := json.Marshal(h) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, fmt.Sprintf("\"%s\"", s), string(bytes)) + }) +} + +func TestSHA256Hash_UnmarshalJSON(t *testing.T) { + s := "452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620" + j := fmt.Sprintf("\"%s\"", s) + + t.Run("ok", func(t *testing.T) { + h := EmptyHash() + err := json.Unmarshal([]byte(j), &h) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, s, h.String()) + }) + + t.Run("error - wrong hex", func(t *testing.T) { + h := EmptyHash() + err := json.Unmarshal([]byte(""), &h) + + assert.Error(t, err) + }) +} + +func TestHash_Equals(t *testing.T) { + t.Run("equal", func(t *testing.T) { + h1, _ := ParseHex("452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620") + h2, _ := ParseHex("452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620") + assert.True(t, h1.Equals(h2)) + }) + t.Run("not equal", func(t *testing.T) { + h1 := EmptyHash() + h2, _ := ParseHex("452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620") + assert.False(t, h1.Equals(h2)) + }) +} + +func TestHash_Compare(t *testing.T) { + t.Run("smaller", func(t *testing.T) { + h1, _ := ParseHex("0000000000000000000000000000000000000000000000000000000000000001") + h2, _ := ParseHex("0000000000000000000000000000000000000000000000000000000000000002") + assert.Equal(t, -1, h1.Compare(h2)) + }) + t.Run("equal", func(t *testing.T) { + h1, _ := ParseHex("0000000000000000000000000000000000000000000000000000000000000001") + h2, _ := ParseHex("0000000000000000000000000000000000000000000000000000000000000001") + assert.Equal(t, 0, h1.Compare(h2)) + }) + t.Run("larger", func(t *testing.T) { + h1, _ := ParseHex("0000000000000000000000000000000000000000000000000000000000000002") + h2, _ := ParseHex("0000000000000000000000000000000000000000000000000000000000000001") + assert.Equal(t, 1, h1.Compare(h2)) + }) +} diff --git a/crypto/interface.go b/crypto/interface.go index 993d152ba6..852a19a77f 100644 --- a/crypto/interface.go +++ b/crypto/interface.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2019. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,14 +22,20 @@ import ( "crypto" ) -// KidNamingFunc is a function passed to New() which generates the kid for the pub/priv key -type KidNamingFunc func(key crypto.PublicKey) string +// KIDNamingFunc is a function passed to New() which generates the kid for the pub/priv key +type KIDNamingFunc func(key crypto.PublicKey) (string, error) -// KeyStore defines the functions than can be called by a Cmd, Direct or via rest call. -type KeyStore interface { +// KeyCreator is the interface for creating key pairs. +type KeyCreator interface { // New generates a keypair and returns the public key. - // the KidNamingFunc will provide the kid. priv/pub keys are appended with a postfix and stored - New(namingFunc KidNamingFunc) (crypto.PublicKey, string, error) + // the KIDNamingFunc will provide the kid. priv/pub keys are appended with a postfix and stored + New(namingFunc KIDNamingFunc) (crypto.PublicKey, string, error) +} + +// KeyStore defines the functions that can be called by a Cmd, Direct or via rest call. +type KeyStore interface { + KeyCreator + // GetPrivateKey returns the specified private key (for e.g. signing) in non-exportable form. // If a key is missing, a Storage.ErrNotFound is returned GetPrivateKey(kid string) (crypto.Signer, error) diff --git a/crypto/jwx.go b/crypto/jwx.go index 9d0e790cf1..00025b75fb 100644 --- a/crypto/jwx.go +++ b/crypto/jwx.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2020. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/crypto/jwx_test.go b/crypto/jwx_test.go index b511801ed3..9d6124d7bd 100644 --- a/crypto/jwx_test.go +++ b/crypto/jwx_test.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2020. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -140,7 +140,7 @@ func TestCrypto_convertHeaders(t *testing.T) { }) t.Run("ok", func(t *testing.T) { - rawHeaders := map[string]interface{} { + rawHeaders := map[string]interface{}{ "key": "value", } diff --git a/crypto/log/log.go b/crypto/log/log.go index 5aaaa6ee87..61dc8e538e 100644 --- a/crypto/log/log.go +++ b/crypto/log/log.go @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020. Nuts community + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/crypto/mock/mock_crypto.go b/crypto/mock.go similarity index 67% rename from crypto/mock/mock_crypto.go rename to crypto/mock.go index 2d0a43db0e..6b29695a18 100644 --- a/crypto/mock/mock_crypto.go +++ b/crypto/mock.go @@ -1,16 +1,54 @@ // Code generated by MockGen. DO NOT EDIT. // Source: crypto/interface.go -// Package mock is a generated GoMock package. -package mock +// Package crypto is a generated GoMock package. +package crypto import ( crypto "crypto" gomock "github.com/golang/mock/gomock" - crypto0 "github.com/nuts-foundation/nuts-node/crypto" reflect "reflect" ) +// MockKeyCreator is a mock of KeyCreator interface +type MockKeyCreator struct { + ctrl *gomock.Controller + recorder *MockKeyCreatorMockRecorder +} + +// MockKeyCreatorMockRecorder is the mock recorder for MockKeyCreator +type MockKeyCreatorMockRecorder struct { + mock *MockKeyCreator +} + +// NewMockKeyCreator creates a new mock instance +func NewMockKeyCreator(ctrl *gomock.Controller) *MockKeyCreator { + mock := &MockKeyCreator{ctrl: ctrl} + mock.recorder = &MockKeyCreatorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockKeyCreator) EXPECT() *MockKeyCreatorMockRecorder { + return m.recorder +} + +// New mocks base method +func (m *MockKeyCreator) New(namingFunc KIDNamingFunc) (crypto.PublicKey, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "New", namingFunc) + ret0, _ := ret[0].(crypto.PublicKey) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// New indicates an expected call of New +func (mr *MockKeyCreatorMockRecorder) New(namingFunc interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockKeyCreator)(nil).New), namingFunc) +} + // MockKeyStore is a mock of KeyStore interface type MockKeyStore struct { ctrl *gomock.Controller @@ -35,7 +73,7 @@ func (m *MockKeyStore) EXPECT() *MockKeyStoreMockRecorder { } // New mocks base method -func (m *MockKeyStore) New(namingFunc crypto0.KidNamingFunc) (crypto.PublicKey, string, error) { +func (m *MockKeyStore) New(namingFunc KIDNamingFunc) (crypto.PublicKey, string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "New", namingFunc) ret0, _ := ret[0].(crypto.PublicKey) @@ -96,15 +134,15 @@ func (mr *MockKeyStoreMockRecorder) SignJWT(claims, kid interface{}) *gomock.Cal } // PrivateKeyExists mocks base method -func (m *MockKeyStore) PrivateKeyExists(key string) bool { +func (m *MockKeyStore) PrivateKeyExists(kid string) bool { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PrivateKeyExists", key) + ret := m.ctrl.Call(m, "PrivateKeyExists", kid) ret0, _ := ret[0].(bool) return ret0 } // PrivateKeyExists indicates an expected call of PrivateKeyExists -func (mr *MockKeyStoreMockRecorder) PrivateKeyExists(key interface{}) *gomock.Call { +func (mr *MockKeyStoreMockRecorder) PrivateKeyExists(kid interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrivateKeyExists", reflect.TypeOf((*MockKeyStore)(nil).PrivateKeyExists), key) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrivateKeyExists", reflect.TypeOf((*MockKeyStore)(nil).PrivateKeyExists), kid) } diff --git a/crypto/storage/fs.go b/crypto/storage/fs.go index 5b509b6426..df289fc1a7 100644 --- a/crypto/storage/fs.go +++ b/crypto/storage/fs.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2019 Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/crypto/storage/storage.go b/crypto/storage/storage.go index 9f12a7b960..4301cd6ecd 100644 --- a/crypto/storage/storage.go +++ b/crypto/storage/storage.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2019 Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/crypto/test.go b/crypto/test.go index 4a9ccbe7d6..00b06d2482 100644 --- a/crypto/test.go +++ b/crypto/test.go @@ -29,8 +29,8 @@ func TestCryptoConfig(testDirectory string) Config { } // StringNamingFunc can be used to give a key a simple string name -func StringNamingFunc(name string) KidNamingFunc { - return func(key crypto.PublicKey) string { - return name +func StringNamingFunc(name string) KIDNamingFunc { + return func(key crypto.PublicKey) (string, error) { + return name, nil } } diff --git a/crypto/util/common.go b/crypto/util/common.go index 8070c7fe8f..275afa26de 100644 --- a/crypto/util/common.go +++ b/crypto/util/common.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2019. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/crypto/util/pem_test.go b/crypto/util/pem_test.go index e592f8f1a9..f11700554f 100644 --- a/crypto/util/pem_test.go +++ b/crypto/util/pem_test.go @@ -1,6 +1,6 @@ /* - * Nuts crypto - * Copyright (C) 2019. Nuts community + * Nuts node + * Copyright (C) 2021. Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/docs/_static/vdr/v1.yaml b/docs/_static/vdr/v1.yaml new file mode 100644 index 0000000000..d1297036d1 --- /dev/null +++ b/docs/_static/vdr/v1.yaml @@ -0,0 +1,229 @@ +openapi: "3.0.0" +info: + title: Nuts Verifiable Data Registry API spec + description: API specification for the Verifiable Data Registry + version: 1.0.0 + license: + name: GPLv3 +paths: + /internal/vdr/v1/did: + post: + summary: Creates a new Nuts DID + operationId: "createDID" + tags: + - DID + responses: + "201": + description: "New DID has been created successfully. Returns the DID Document." + content: + application/json+did-document: + schema: + $ref: '#/components/schemas/DIDDocument' + "500": + description: An error occurred while processing the request. + content: + text/plain: + schema: + type: string + /internal/vdr/v1/did/{did}: + parameters: + - name: did + in: path + description: URL encoded DID. + required: true + example: + - "did:nuts:1234" + schema: + type: string + get: + summary: "Resolves a Nuts DID Document" + operationId: "getDID" + tags: + - DID + responses: + "200": + description: DID has been found and returned. + content: + application/json: + $ref: '#/components/schemas/DIDResolutionResult' + "400": + description: Returned in case of malformed DID. + content: + text/plain: + schema: + type: string + "404": + description: Corresponding DID document could not be found. + content: + text/plain: + schema: + type: string + "500": + description: An error occurred while processing the request. + content: + text/plain: + schema: + type: string + put: + summary: Updates a Nuts DID Document. + operationId: "updateDID" + tags: + - DID + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DIDUpdateRequest' + responses: + "200": + description: DID Document has been updated. + content: + application/json+did-document: + schema: + $ref: '#/components/schemas/DIDDocument' + "400": + description: DID couldn't be updated because the input conflicts or is malformed. + content: + text/plain: + schema: + type: string + "404": + description: Corresponding DID document could not be found. + content: + text/plain: + schema: + type: string + "500": + description: An error occurred while processing the request. + content: + text/plain: + schema: + type: string + +components: + schemas: + DIDDocument: + type: object + description: A DID Document according to the W3C spec following the Nuts Method rules as defined in [Nuts RFC006] + required: + - id + properties: + assertionMethod: + description: List of KIDs that may sign JWTs, JWSs and VCs + type: array + items: + type: string + authentication: + description: List of KIDs that may alter DID documents that they control + type: array + items: + type: string + context: + description: List of URIs + type: array + items: + type: string + controller: + description: List of DIDs that have control over the DID Document + type: array + items: + type: string + id: + description: DID according to Nuts specification + example: "did:nuts:1" + type: string + service: + description: List of supported services by the DID subject + type: array + items: + $ref: '#/components/schemas/Service' + verificationMethod: + description: list of keys + type: array + items: + $ref: '#/components/schemas/VerificationMethod' + DIDDocumentMetadata: + type: object + description: The DID Document metadata. + required: + - created + - hash + - originJWSHash + - version + properties: + created: + description: Time when DID Document was created in rfc3339 form. + type: string + hash: + description: Sha256 in hex form of the DID document contents. + type: string + originJWSHash: + description: Sha256 in hex form of the transaction in which the DID Document was published. + type: string + updated: + description: Time when DID Document was updated in rfc3339 form. + type: string + version: + description: Version of the DID Document, starting at 1. + type: integer + DIDResolutionResult: + required: + - document + - documentMetadata + properties: + document: + $ref: '#/components/schemas/DIDDocument' + documentMetadata: + $ref: '#/components/schemas/DIDDocumentMetadata' + Service: + type: object + description: A service supported by a DID subject. + required: + - id + - type + - serviceEndpoint + properties: + id: + description: ID of the service. + type: string + type: + description: The type of the endpoint. + type: string + serviceEndpoint: + description: Either a URI or a complex object. + oneOf: + - type: string + - type: object + DIDUpdateRequest: + required: + - document + - currentHash + properties: + document: + $ref: '#/components/schemas/DIDDocument' + currentHash: + type: string + description: The hash of the document in hex format. + VerificationMethod: + description: A public key in JWK form. + required: + - id + - type + - controller + - publicKeyJwk + properties: + controller: + description: The DID subject this key belongs to. + example: "did:nuts:1" + type: string + id: + description: The ID of the key, used as KID in various JWX technologies. + type: string + publicKeyJwk: + description: The public key formatted according rfc7517. + type: object + type: + description: The type of the key. + example: "JsonWebKey2020" + type: string \ No newline at end of file diff --git a/docs/pages/api.rst b/docs/pages/api.rst index 1a01c16117..f6fc4f9f64 100644 --- a/docs/pages/api.rst +++ b/docs/pages/api.rst @@ -14,7 +14,8 @@ Nuts APIs const ui = SwaggerUIBundle({ "dom_id": "#swagger-ui", urls: [ - {url: "../../_static/nuts-example.yaml", name: "example"}, + {url: "../_static/crypto/v1.yaml", name: "Crypto"}, + {url: "../_static/vdr/v1.yaml", name: "Verifiable Data Registry"}, ], presets: [ SwaggerUIBundle.presets.apis, diff --git a/docs/pages/configuration/options.rst b/docs/pages/configuration/options.rst index 8aae0df778..603e825142 100755 --- a/docs/pages/configuration/options.rst +++ b/docs/pages/configuration/options.rst @@ -45,16 +45,9 @@ network.mode network.nodeID Instance ID of this node under which the public address is registered on the nodelist. If not set, the Nuts node's identity will be used. network.publicAddr Public address (of this node) other nodes can use to connect to it. If set, it is registered on the nodelist. network.storageConnectionString file:network.db SQLite3 connection string to the database where the network should persist its documents. -**Registry** -registry.address localhost:1323 Interface and port for http server to bind to, default: localhost:1323 -registry.clientTimeout 10 Time-out for the client in seconds (e.g. when using the CLI), default: 10 -registry.datadir ./data Location of data files, default: ./data -registry.mode server server or client, when client it uses the HttpClient, default: server -registry.organisationCertificateValidity 365 Number of days organisation certificates are valid, default: 365 -registry.syncAddress https://codeload.github.com/nuts-foundation/nuts-registry-development/tar.gz/master The remote url to download the latest registry data from, default: https://codeload.github.com/nuts-foundation/nuts-registry-development/tar.gz/master -registry.syncInterval 30 The interval in minutes between looking for updated registry files on github, default: 30 -registry.syncMode fs The method for updating the data, 'fs' for a filesystem watch or 'github' for a periodic download, default: fs -registry.vendorCACertificateValidity 1095 Number of days vendor CA certificates are valid, default: 1095 -**Validation** +**VDR** +vdr.clientTimeout 10 Time-out for the client in seconds (e.g. when using the CLI), default: 10 +vdr.datadir ./data Location of data files, default: ./data +**Validation** fhir.schemapath location of json schema, default nested Asset -======================================== =================================================================================== ================================================================================================================================================================================ +=========================================================================================================================== ================================================================================================================================================================================ diff --git a/docs/pages/development.rst b/docs/pages/development.rst index 2bdbdc5fc8..6c6c7328cf 100644 --- a/docs/pages/development.rst +++ b/docs/pages/development.rst @@ -28,7 +28,7 @@ The server and client API is generated from the open-api spec: .. code-block:: shell - oapi-codegen -generate types,server,client -package v1 docs/_static/crypto/v1.yaml > api/crypto/v1/generated.go + make gen-api Generating Mocks **************** @@ -37,8 +37,7 @@ These mocks are used by other modules .. code-block:: shell - mockgen -destination=crypto/mock/mock_crypto.go -package=mock -source=crypto/interface.go KeyStore - + make gen-mocks README ****** @@ -46,7 +45,7 @@ The readme is auto-generated from a template and uses the documentation to fill .. code-block:: shell - ./generate_readme.sh + make gen-readme This script uses ``rst_include`` which is installed as part of the dependencies for generating the documentation. diff --git a/go.mod b/go.mod index 08d9de0b13..7dc70828fb 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,15 @@ go 1.15 require ( github.com/deepmap/oapi-codegen v1.4.2 - github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/golang/mock v1.4.4 github.com/labstack/echo/v4 v4.1.17 github.com/lestrrat-go/jwx v1.0.7 github.com/magiconair/properties v1.8.4 + github.com/nuts-foundation/go-did v0.0.0-20210125091337-21da6ad69a82 + github.com/nuts-foundation/nuts-network v0.16.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.7.1 + github.com/shengdoushi/base58 v1.0.0 github.com/sirupsen/logrus v1.7.0 github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index e890b9b161..7a947f419f 100644 --- a/go.sum +++ b/go.sum @@ -1,76 +1,148 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/spanner v1.2.0/go.mod h1:LfwGAsK42Yz8IeLsd/oagGFBqTXt3xVWtm8/KD2vrEI= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= +github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deepmap/oapi-codegen v1.4.1/go.mod h1:1jY0YDxfBF3tXk1u3sARJMSUJa9wV0UrVT6o+2mr/zQ= github.com/deepmap/oapi-codegen v1.4.2 h1:boVMuW+o0sOnEDB8oWRobBm1BrD5d9bUtIQVcjwMsSk= github.com/deepmap/oapi-codegen v1.4.2/go.mod h1:1jY0YDxfBF3tXk1u3sARJMSUJa9wV0UrVT6o+2mr/zQ= +github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dhui/dktest v0.3.2/go.mod h1:l1/ib23a/CmxAe7yixtrYPc8Iy90Zy2udyaHINM5p58= +github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20200213202729-31a86c4ab209/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= github.com/getkin/kin-openapi v0.26.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang-migrate/migrate/v4 v4.11.0 h1:uqtd0ysK5WyBQ/T1K2uDIooJV0o2Obt6uPwP062DupQ= +github.com/golang-migrate/migrate/v4 v4.11.0/go.mod h1:nqbpDbckcYjsCD5I8q5+NI9Tkk7SVcmaF40Ax1eAWhg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -78,6 +150,7 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -85,6 +158,9 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -93,20 +169,30 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= @@ -116,6 +202,8 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= @@ -130,22 +218,61 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.3.2/go.mod h1:LvCquS3HbBKwgl7KbX9KyqEIumJAbm1UMcTvGaIf3bM= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/gorm v1.9.14 h1:Kg3ShyTPcM6nzVo148fRrcMO6MNKuqtOUwnzqMgVniM= +github.com/jinzhu/gorm v1.9.14/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= @@ -159,29 +286,47 @@ github.com/lestrrat-go/httpcc v0.0.0-20210101035852-e7e8fea419e3 h1:e52qvXxpJPV/ github.com/lestrrat-go/httpcc v0.0.0-20210101035852-e7e8fea419e3/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911 h1:FvnrqecqX4zT0wOIbYK1gNgTm0677INEWiFY8UEYggY= github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.0.5/go.mod h1:TPF17WiSFegZo+c20fdpw49QD+/7n4/IsGvEmCSWwT0= github.com/lestrrat-go/jwx v1.0.7 h1:wmd6LGb9mYPesObp7oipuZPE1aYYJ1VeUin8LTVNy24= github.com/lestrrat-go/jwx v1.0.7/go.mod h1:6XJ5sxHF5U116AxYxeHfTnfsZRMgmeKY214zwZDdvho= github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35 h1:lea8Wt+1ePkVrI2/WD+NgQT5r/XsLAzxeqtyFLcEs10= github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/pdebug v0.0.0-20200204225717-4d6bd78da58d h1:aEZT3f1GGg5RIlHMAy4/4fe4ciOi3SCwYoaURphcB4k= +github.com/lestrrat-go/pdebug v0.0.0-20200204225717-4d6bd78da58d/go.mod h1:B06CSso/AWxiPejj+fheUINGeBKeeEZNt8w+EoU7+L8= github.com/lestrrat-go/pdebug/v3 v3.0.0-20210111091911-ec4f5c88c087 h1:T5Wh8C/p5nWoGuEUBQj+daEXkj1CScB9GshvvsBJhpg= github.com/lestrrat-go/pdebug/v3 v3.0.0-20210111091911-ec4f5c88c087/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY= github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -198,11 +343,34 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= +github.com/neo4j-drivers/gobolt v1.7.4/go.mod h1:O9AUbip4Dgre+CD3p40dnMD4a4r52QBIfblg5k7CTbE= +github.com/neo4j/neo4j-go-driver v1.7.4/go.mod h1:aPO0vVr+WnhEJne+FgFjfsjzAnssPFLucHgGZ76Zb/U= +github.com/nuts-foundation/go-did v0.0.0-20210125091337-21da6ad69a82 h1:AUj8bNaFcd2mR1+38hIrJU4oOs8Y7YDSElPWS+srTF8= +github.com/nuts-foundation/go-did v0.0.0-20210125091337-21da6ad69a82/go.mod h1:wpgiNMle93QRhzm2Oh4J+yGgbSpw1LoBAcsoUqNr2Bo= +github.com/nuts-foundation/nuts-crypto v0.16.0 h1:PzNDcoawuGxl4KRoD3nKnz+1sFiMbEG/S95mYbbso6k= +github.com/nuts-foundation/nuts-crypto v0.16.0/go.mod h1:NS4akgWLGO8RwtzMj2gXDEpucV0S6QDeyLxEkzGSyd4= +github.com/nuts-foundation/nuts-go-core v0.16.0 h1:bHKH0eREWecXLWUexC676RACsLdz/B2tuhkNFzi21FM= +github.com/nuts-foundation/nuts-go-core v0.16.0/go.mod h1:biWHwDgHorQ6diimNbfFFqM/bub27g5/a6IZdUdojS4= +github.com/nuts-foundation/nuts-go-test v0.16.0 h1:jfbh2z44FAsRSPXquTZWviOlB9xb9YeJDzBfPMR5Xz0= +github.com/nuts-foundation/nuts-go-test v0.16.0/go.mod h1:aZ5Urj7v1lH8AD2TIejEiPhRX8t6YG9PVXhuRyNIAII= +github.com/nuts-foundation/nuts-network v0.16.0 h1:JtSuHXoqB2rLHhTDBocyqDaS11mCpp/V32nF+XEbEk8= +github.com/nuts-foundation/nuts-network v0.16.0/go.mod h1:oKrpBUpKoKSQRbEaHkp58yKEnYHkxb75CbJUXuVJSMg= +github.com/ockam-network/did v0.1.3 h1:qJGdccOV4bLfsS/eFM+Aj+CdCRJKNMxbmJevQclw44k= +github.com/ockam-network/did v0.1.3/go.mod h1:ZsbTIuVGt8OrQEbqWrSztUISN4joeMabdsinbLubbzw= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -212,35 +380,51 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v0.9.4/go.mod h1:oCXIBxdI62A4cR6aTRJCgetEjecSIYzOEaeAn4iYEpM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shengdoushi/base58 v1.0.0 h1:tGe4o6TmdXFJWoI31VoSWvuaKxf0Px3gqa3sUWhAxBs= +github.com/shengdoushi/base58 v1.0.0/go.mod h1:m5uIILfzcKMw6238iWAhP4l3s5+uXyF3+bJKUNhAL9I= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -252,12 +436,17 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -266,6 +455,7 @@ github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -274,6 +464,7 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -282,23 +473,40 @@ github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -309,6 +517,11 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200213203834-85f925bdd4d0/go.mod h1:IX6Eufr4L0ErOUlzqX/aFlHqsiKZRbV42Kb69e9VsTE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -318,18 +531,26 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -339,13 +560,24 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200927032502-5d4f70055728 h1:5wtQIAulKU5AbLQOkjxl32UufnIOqgBX72pS0AV14H0= +golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -355,30 +587,45 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirSiguP9Q/veMtkYyf0o8= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200928205150-006507a75852 h1:sXxgOAXy8JwHhZnPuItAlUtwIlxrlEqi28mKhUR+zZY= +golang.org/x/sys v0.0.0-20200928205150-006507a75852/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -387,7 +634,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -395,32 +644,58 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200128002243-345141a36859/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200213224642-88e652f7a869/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -428,10 +703,27 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200507105951-43844f6eee31 h1:Bz1qTn2YRWV+9OKJtxHJiQKCiXIdf+kwuKXdt9cBxyU= +google.golang.org/genproto v0.0.0-20200507105951-43844f6eee31/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -445,21 +737,44 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= +modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8= +modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw= +modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= +modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= +modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= +modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go index 318f81d67c..7dd9e3e7bd 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,6 @@ /* - * Nuts go - * Copyright (C) 2019 Nuts community + * Nuts node + * Copyright (C) 2021 Nuts community * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/makefile b/makefile index 9dd8ec2108..2adf167ff9 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,22 @@ +.PHONY: test run-generators update-docs + +run-generators: gen-readme gen-mocks gen-api + +gen-readme: + ./generate_readme.sh + +gen-mocks: + mockgen -destination=crypto/mock.go -package=crypto -source=crypto/interface.go -self_package github.com/nuts-foundation/nuts-node/crypto + mockgen -destination=vdr/types/mock.go -package=types -source=vdr/types/interface.go -self_package github.com/nuts-foundation/nuts-node/vdr/types --imports did=github.com/nuts-foundation/go-did + +gen-api: + oapi-codegen -generate types,server,client -package v1 docs/_static/crypto/v1.yaml > crypto/api/v1/generated.go + oapi-codegen -generate types,server,client,skip-prune -package v1 -exclude-schemas DIDDocument,DIDDocumentMetadata,Service,VerificationMethod docs/_static/vdr/v1.yaml > vdr/api/v1/generated.go + +gen-docs: + go run ./docs + test: go test ./... -update-docs: - go run ./docs - ./generate_readme.sh +update-docs: gen-docs gen-readme diff --git a/test/http/handler.go b/test/http/handler.go new file mode 100644 index 0000000000..8b23ad3bf8 --- /dev/null +++ b/test/http/handler.go @@ -0,0 +1,46 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package http + +import ( + "encoding/json" + "net/http" +) + +// Handler is a custom http handler useful in testing. +// Usage: +// s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: someStruct}) +// Then: +// s.URL +// must be configured in the client. +type Handler struct { + StatusCode int + ResponseData interface{} +} + +func (h Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(h.StatusCode) + + var bytes []byte + if s, ok := h.ResponseData.(string); ok { + bytes = []byte(s) + } else { + bytes, _ = json.Marshal(h.ResponseData) + } + + writer.Write(bytes) +} diff --git a/vdr/api/v1/api.go b/vdr/api/v1/api.go new file mode 100644 index 0000000000..6f33f17955 --- /dev/null +++ b/vdr/api/v1/api.go @@ -0,0 +1,100 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package v1 + +import ( + "errors" + "fmt" + "net/http" + + "github.com/labstack/echo/v4" + did2 "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-node/crypto/hash" + "github.com/nuts-foundation/nuts-node/vdr/types" +) + +// Wrapper is needed to connect the implementation to the echo ServiceWrapper +type Wrapper struct { + VDR types.VDR +} + +// CreateDID creates a new DID Document and returns it. +func (a Wrapper) CreateDID(ctx echo.Context) error { + doc, err := a.VDR.Create() + // if this operation leads to an error, it may return a 500 + if err != nil { + return err + } + + // this API returns a DIDDocument according to spec so it may return the business object + return ctx.JSON(http.StatusOK, *doc) +} + +// GetDID returns a DID document and DID document metadata based on a DID. +func (a Wrapper) GetDID(ctx echo.Context, did string) error { + d, err := did2.ParseDID(did) + if err != nil { + return ctx.String(http.StatusBadRequest, fmt.Sprintf("given DID could not be parsed: %s", err.Error())) + } + + // no params in the API for now + doc, meta, err := a.VDR.Resolve(*d, nil) + if err != nil { + if errors.Is(err, types.ErrNotFound) { + return ctx.NoContent(http.StatusNotFound) + } + return err + } + + resolutionResult := DIDResolutionResult{ + Document: *doc, + DocumentMetadata: *meta, + } + + return ctx.JSON(http.StatusOK, resolutionResult) +} + +// UpdateDID updates a DID Document given a DID and DID Document body. It returns the updated DID Document. +func (a Wrapper) UpdateDID(ctx echo.Context, did string) error { + d, err := did2.ParseDID(did) + if err != nil { + return ctx.String(http.StatusBadRequest, fmt.Sprintf("given DID could not be parsed: %s", err.Error())) + } + + req := DIDUpdateRequest{} + if err := ctx.Bind(&req); err != nil { + return ctx.String(http.StatusBadRequest, fmt.Sprintf("given update request could not be parsed: %s", err.Error())) + } + + h, err := hash.ParseHex(req.CurrentHash) + if err != nil { + return ctx.String(http.StatusBadRequest, fmt.Sprintf("given hash is not valid: %s", err.Error())) + } + + if err := a.VDR.Update(*d, h, req.Document, nil); err != nil { + // for middleware maybe + if errors.Is(err, types.ErrNotFound) { + return ctx.NoContent(http.StatusNotFound) + } + return ctx.String(http.StatusBadRequest, fmt.Sprintf("couldn't update DID document: %s", err.Error())) + } + + return ctx.JSON(http.StatusOK, req.Document) +} diff --git a/vdr/api/v1/api_test.go b/vdr/api/v1/api_test.go new file mode 100644 index 0000000000..27dd13cdcd --- /dev/null +++ b/vdr/api/v1/api_test.go @@ -0,0 +1,248 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package v1 + +import ( + "errors" + "net/http" + "testing" + + "github.com/golang/mock/gomock" + did2 "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-node/mock" + "github.com/nuts-foundation/nuts-node/vdr/types" + "github.com/stretchr/testify/assert" +) + +func TestWrapper_CreateDID(t *testing.T) { + did, _ := did2.ParseDID("did:nuts:1") + didDoc := &did2.Document{ + ID: *did, + } + + t.Run("ok", func(t *testing.T) { + ctx := newMockContext(t) + defer ctx.ctrl.Finish() + + var didDocReturn did2.Document + ctx.echo.EXPECT().JSON(http.StatusOK, gomock.Any()).DoAndReturn(func(f interface{}, f2 interface{}) error { + didDocReturn = f2.(did2.Document) + return nil + }) + ctx.vdr.EXPECT().Create().Return(didDoc, nil) + err := ctx.client.CreateDID(ctx.echo) + + if !assert.NoError(t, err) { + return + } + assert.Equal(t, *did, didDocReturn.ID) + }) + + t.Run("error - 500", func(t *testing.T) { + ctx := newMockContext(t) + defer ctx.ctrl.Finish() + + ctx.vdr.EXPECT().Create().Return(nil, errors.New("b00m!")) + err := ctx.client.CreateDID(ctx.echo) + + assert.Error(t, err) + }) +} + +func TestWrapper_GetDID(t *testing.T) { + did, _ := did2.ParseDID("did:nuts:1") + didDoc := &did2.Document{ + ID: *did, + } + meta := &types.DocumentMetadata{} + + t.Run("ok", func(t *testing.T) { + ctx := newMockContext(t) + defer ctx.ctrl.Finish() + + didResolutionResult := DIDResolutionResult{} + ctx.echo.EXPECT().JSON(http.StatusOK, gomock.Any()).DoAndReturn(func(f interface{}, f2 interface{}) error { + didResolutionResult = f2.(DIDResolutionResult) + return nil + }) + + ctx.vdr.EXPECT().Resolve(*did, nil).Return(didDoc, meta, nil) + err := ctx.client.GetDID(ctx.echo, did.String()) + + if !assert.NoError(t, err) { + return + } + assert.Equal(t, *did, didResolutionResult.Document.ID) + }) + + t.Run("error - wrong did format", func(t *testing.T) { + ctx := newMockContext(t) + defer ctx.ctrl.Finish() + + ctx.echo.EXPECT().String(http.StatusBadRequest, gomock.Any()) + err := ctx.client.GetDID(ctx.echo, "not a did") + + assert.NoError(t, err) + }) + + t.Run("error - not found", func(t *testing.T) { + ctx := newMockContext(t) + defer ctx.ctrl.Finish() + + ctx.echo.EXPECT().NoContent(http.StatusNotFound) + ctx.vdr.EXPECT().Resolve(*did, nil).Return(nil, nil, types.ErrNotFound) + err := ctx.client.GetDID(ctx.echo, did.String()) + + assert.NoError(t, err) + }) + + t.Run("error - other", func(t *testing.T) { + ctx := newMockContext(t) + defer ctx.ctrl.Finish() + + ctx.vdr.EXPECT().Resolve(*did, nil).Return(nil, nil, errors.New("b00m!")) + err := ctx.client.GetDID(ctx.echo, did.String()) + + assert.Error(t, err) + }) +} + +func TestWrapper_UpdateDID(t *testing.T) { + did, _ := did2.ParseDID("did:nuts:1") + didDoc := &did2.Document{ + ID: *did, + } + didUpdate := DIDUpdateRequest{ + Document: *didDoc, + CurrentHash: "452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620", + } + + t.Run("ok", func(t *testing.T) { + ctx := newMockContext(t) + defer ctx.ctrl.Finish() + + var didReturn did2.Document + ctx.echo.EXPECT().Bind(gomock.Any()).DoAndReturn(func(f interface{}) error { + p := f.(*DIDUpdateRequest) + *p = didUpdate + return nil + }) + ctx.echo.EXPECT().JSON(http.StatusOK, gomock.Any()).DoAndReturn(func(f interface{}, f2 interface{}) error { + didReturn = f2.(did2.Document) + return nil + }) + ctx.vdr.EXPECT().Update(*did, gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + err := ctx.client.UpdateDID(ctx.echo, did.String()) + + if !assert.NoError(t, err) { + return + } + assert.Equal(t, *did, didReturn.ID) + }) + + t.Run("error - wrong did format", func(t *testing.T) { + ctx := newMockContext(t) + defer ctx.ctrl.Finish() + + ctx.echo.EXPECT().String(http.StatusBadRequest, gomock.Any()) + err := ctx.client.UpdateDID(ctx.echo, "not a did") + + assert.NoError(t, err) + }) + + t.Run("error - not found", func(t *testing.T) { + ctx := newMockContext(t) + defer ctx.ctrl.Finish() + + ctx.echo.EXPECT().Bind(gomock.Any()).DoAndReturn(func(f interface{}) error { + p := f.(*DIDUpdateRequest) + *p = didUpdate + return nil + }) + ctx.echo.EXPECT().NoContent(http.StatusNotFound) + ctx.vdr.EXPECT().Update(*did, gomock.Any(), gomock.Any(), gomock.Any()).Return(types.ErrNotFound) + err := ctx.client.UpdateDID(ctx.echo, did.String()) + + assert.NoError(t, err) + }) + + t.Run("error - other", func(t *testing.T) { + ctx := newMockContext(t) + defer ctx.ctrl.Finish() + + ctx.echo.EXPECT().Bind(gomock.Any()).DoAndReturn(func(f interface{}) error { + p := f.(*DIDUpdateRequest) + *p = didUpdate + return nil + }) + ctx.echo.EXPECT().String(http.StatusBadRequest, gomock.Any()) + ctx.vdr.EXPECT().Update(*did, gomock.Any(), gomock.Any(), gomock.Any()).Return(errors.New("b00m!")) + err := ctx.client.UpdateDID(ctx.echo, did.String()) + + assert.NoError(t, err) + }) + + t.Run("error - wrong hash", func(t *testing.T) { + ctx := newMockContext(t) + defer ctx.ctrl.Finish() + + didUpdate := DIDUpdateRequest{ + Document: *didDoc, + CurrentHash: "0", + } + + ctx.echo.EXPECT().Bind(gomock.Any()).DoAndReturn(func(f interface{}) error { + p := f.(*DIDUpdateRequest) + *p = didUpdate + return nil + }) + ctx.echo.EXPECT().String(http.StatusBadRequest, gomock.Any()) + err := ctx.client.UpdateDID(ctx.echo, did.String()) + + assert.NoError(t, err) + }) + + t.Run("error - bind goes wrong", func(t *testing.T) { + ctx := newMockContext(t) + defer ctx.ctrl.Finish() + + ctx.echo.EXPECT().Bind(gomock.Any()).Return(errors.New("b00m!")) + ctx.echo.EXPECT().String(http.StatusBadRequest, gomock.Any()) + err := ctx.client.UpdateDID(ctx.echo, did.String()) + + assert.NoError(t, err) + }) +} + +type mockContext struct { + ctrl *gomock.Controller + echo *mock.MockContext + vdr *types.MockVDR + client *Wrapper +} + +func newMockContext(t *testing.T) mockContext { + ctrl := gomock.NewController(t) + vdr := types.NewMockVDR(ctrl) + client := &Wrapper{VDR: vdr} + + return mockContext{ + ctrl: ctrl, + echo: mock.NewMockContext(ctrl), + vdr: vdr, + client: client, + } +} diff --git a/vdr/api/v1/client.go b/vdr/api/v1/client.go new file mode 100644 index 0000000000..e26c070939 --- /dev/null +++ b/vdr/api/v1/client.go @@ -0,0 +1,144 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package v1 + +import ( + "context" + "encoding/json" + "fmt" + "io" + + "github.com/nuts-foundation/go-did" + + "io/ioutil" + "net/http" + "time" +) + +// HTTPClient holds the server address and other basic settings for the http client +type HTTPClient struct { + ServerAddress string + Timeout time.Duration +} + +func (hb HTTPClient) client() ClientInterface { + url := hb.ServerAddress + + response, err := NewClientWithResponses(url) + if err != nil { + panic(err) + } + return response +} + +// Create calls the server and creates a new DID Document +func (hb HTTPClient) Create() (*did.Document, error) { + ctx, cancel := hb.withTimeout() + defer cancel() + + if response, err := hb.client().CreateDID(ctx); err != nil { + return nil, err + } else if err := testResponseCode(http.StatusOK, response); err != nil { + return nil, err + } else { + return readDIDDocument(response.Body) + } +} + +// Get returns a DID document and metadata based on a DID +func (hb HTTPClient) Get(DID string) (*DIDDocument, *DIDDocumentMetadata, error) { + ctx, cancel := hb.withTimeout() + defer cancel() + + response, err := hb.client().GetDID(ctx, DID) + if err != nil { + return nil, nil, err + } + if err := testResponseCode(http.StatusOK, response); err != nil { + return nil, nil, err + } + + var resolutionResult *DIDResolutionResult + if resolutionResult, err = readDIDResolutionResult(response.Body); err != nil { + return nil, nil, err + } + return &resolutionResult.Document, &resolutionResult.DocumentMetadata, nil +} + +// Update a DID Document given a DID and its current hash. +func (hb HTTPClient) Update(DID string, current string, next did.Document) (*did.Document, error) { + ctx, cancel := hb.withTimeout() + defer cancel() + + requestBody := UpdateDIDJSONRequestBody{ + Document: next, + CurrentHash: current, + } + response, err := hb.client().UpdateDID(ctx, DID, requestBody) + if err != nil { + return nil, err + } + if err := testResponseCode(http.StatusOK, response); err != nil { + return nil, err + } + + return readDIDDocument(response.Body) +} + +func (hb HTTPClient) withTimeout() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), hb.Timeout) +} + +func testResponseCode(expectedStatusCode int, response *http.Response) error { + if response.StatusCode != expectedStatusCode { + responseData, _ := ioutil.ReadAll(response.Body) + return fmt.Errorf("registry returned HTTP %d (expected: %d), response: %s", + response.StatusCode, expectedStatusCode, string(responseData)) + } + return nil +} + +func readDIDDocument(reader io.Reader) (*did.Document, error) { + var data []byte + var err error + + if data, err = ioutil.ReadAll(reader); err != nil { + return nil, fmt.Errorf("unable to read DID Document response: %w", err) + } + document := did.Document{} + if err = json.Unmarshal(data, &document); err != nil { + return nil, fmt.Errorf("unable to unmarshal DID Document response: %w, %s", err, string(data)) + } + return &document, nil +} + +func readDIDResolutionResult(reader io.Reader) (*DIDResolutionResult, error) { + var data []byte + var err error + + if data, err = ioutil.ReadAll(reader); err != nil { + return nil, fmt.Errorf("unable to read DID Resolve response: %w", err) + } + resolutionResult := DIDResolutionResult{} + if err = json.Unmarshal(data, &resolutionResult); err != nil { + return nil, fmt.Errorf("unable to unmarshal DID Resolve response: %w", err) + } + return &resolutionResult, nil +} diff --git a/vdr/api/v1/client_test.go b/vdr/api/v1/client_test.go new file mode 100644 index 0000000000..8b22c659c2 --- /dev/null +++ b/vdr/api/v1/client_test.go @@ -0,0 +1,170 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package v1 + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + "time" + + did2 "github.com/nuts-foundation/go-did" + http2 "github.com/nuts-foundation/nuts-node/test/http" + "github.com/nuts-foundation/nuts-node/vdr/types" + "github.com/stretchr/testify/assert" +) + +func TestHTTPClient_Create(t *testing.T) { + did, _ := did2.ParseDID("did:nuts:1") + didDoc := did2.Document{ + ID: *did, + } + + t.Run("ok", func(t *testing.T) { + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusOK, ResponseData: didDoc}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + doc, err := c.Create() + if !assert.NoError(t, err) { + return + } + assert.NotNil(t, doc) + }) + + t.Run("error - server error", func(t *testing.T) { + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusInternalServerError, ResponseData: ""}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + _, err := c.Create() + assert.Error(t, err) + }) + + t.Run("error - wrong address", func(t *testing.T) { + c := HTTPClient{ServerAddress: "not_an_address", Timeout: time.Second} + _, err := c.Create() + assert.Error(t, err) + }) +} + +func TestHttpClient_Get(t *testing.T) { + didString := "did:nuts:1" + did, _ := did2.ParseDID(didString) + didDoc := did2.Document{ + ID: *did, + } + meta := types.DocumentMetadata{} + + t.Run("ok", func(t *testing.T) { + resolutionResult := DIDResolutionResult{ + Document: didDoc, + DocumentMetadata: meta, + } + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusOK, ResponseData: resolutionResult}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + doc, meta, err := c.Get(didString) + if !assert.NoError(t, err) { + return + } + assert.NotNil(t, doc) + assert.NotNil(t, meta) + }) + + t.Run("error - not found", func(t *testing.T) { + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusNotFound, ResponseData: ""}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + + _, _, err := c.Get(didString) + + assert.Error(t, err) + }) + + t.Run("error - invalid response", func(t *testing.T) { + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusOK, ResponseData: "}"}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + + _, _, err := c.Get(didString) + + assert.Error(t, err) + }) + + t.Run("error - wrong address", func(t *testing.T) { + c := HTTPClient{ServerAddress: "not_an_address", Timeout: time.Second} + _, _, err := c.Get(didString) + assert.Error(t, err) + }) +} + +func TestHTTPClient_Update(t *testing.T) { + didString := "did:nuts:1" + did, _ := did2.ParseDID(didString) + didDoc := did2.Document{ + ID: *did, + } + hash := "0000000000000000000000000000000000000000" + + t.Run("ok", func(t *testing.T) { + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusOK, ResponseData: didDoc}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + doc, err := c.Update(didString, hash, didDoc) + if !assert.NoError(t, err) { + return + } + assert.NotNil(t, doc) + }) + + t.Run("error - not found", func(t *testing.T) { + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusNotFound, ResponseData: ""}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + + _, err := c.Update(didString, hash, didDoc) + + assert.Error(t, err) + }) + + t.Run("error - invalid response", func(t *testing.T) { + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusOK, ResponseData: "}"}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + + _, err := c.Update(didString, hash, didDoc) + + assert.Error(t, err) + }) + + t.Run("error - wrong address", func(t *testing.T) { + c := HTTPClient{ServerAddress: "not_an_address", Timeout: time.Second} + _, err := c.Update(didString, hash, didDoc) + assert.Error(t, err) + }) +} + +func TestReadDIDDocument(t *testing.T) { + t.Run("error - faulty stream", func(t *testing.T) { + _, err := readDIDDocument(errReader{}) + assert.Error(t, err) + }) +} + +func TestReadDIDResolutionResult(t *testing.T) { + t.Run("error - faulty stream", func(t *testing.T) { + _, err := readDIDResolutionResult(errReader{}) + assert.Error(t, err) + }) +} + +type errReader struct{} + +func (e errReader) Read(_ []byte) (n int, err error) { + return 0, errors.New("b00m!") +} diff --git a/vdr/api/v1/generated.go b/vdr/api/v1/generated.go new file mode 100644 index 0000000000..bbabab0a2b --- /dev/null +++ b/vdr/api/v1/generated.go @@ -0,0 +1,587 @@ +// Package v1 provides primitives to interact the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT. +package v1 + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + + "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/labstack/echo/v4" +) + +// DIDResolutionResult defines model for DIDResolutionResult. +type DIDResolutionResult struct { + + // A DID Document according to the W3C spec following the Nuts Method rules as defined in [Nuts RFC006] + Document DIDDocument `json:"document"` + + // The DID Document metadata. + DocumentMetadata DIDDocumentMetadata `json:"documentMetadata"` +} + +// DIDUpdateRequest defines model for DIDUpdateRequest. +type DIDUpdateRequest struct { + + // The hash of the document in hex format. + CurrentHash string `json:"currentHash"` + + // A DID Document according to the W3C spec following the Nuts Method rules as defined in [Nuts RFC006] + Document DIDDocument `json:"document"` +} + +// UpdateDIDJSONBody defines parameters for UpdateDID. +type UpdateDIDJSONBody DIDUpdateRequest + +// UpdateDIDRequestBody defines body for UpdateDID for application/json ContentType. +type UpdateDIDJSONRequestBody UpdateDIDJSONBody + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A callback for modifying requests which are generated before sending over + // the network. + RequestEditor RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = http.DefaultClient + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditor = fn + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // CreateDID request + CreateDID(ctx context.Context) (*http.Response, error) + + // GetDID request + GetDID(ctx context.Context, did string) (*http.Response, error) + + // UpdateDID request with any body + UpdateDIDWithBody(ctx context.Context, did string, contentType string, body io.Reader) (*http.Response, error) + + UpdateDID(ctx context.Context, did string, body UpdateDIDJSONRequestBody) (*http.Response, error) +} + +func (c *Client) CreateDID(ctx context.Context) (*http.Response, error) { + req, err := NewCreateDIDRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if c.RequestEditor != nil { + err = c.RequestEditor(ctx, req) + if err != nil { + return nil, err + } + } + return c.Client.Do(req) +} + +func (c *Client) GetDID(ctx context.Context, did string) (*http.Response, error) { + req, err := NewGetDIDRequest(c.Server, did) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if c.RequestEditor != nil { + err = c.RequestEditor(ctx, req) + if err != nil { + return nil, err + } + } + return c.Client.Do(req) +} + +func (c *Client) UpdateDIDWithBody(ctx context.Context, did string, contentType string, body io.Reader) (*http.Response, error) { + req, err := NewUpdateDIDRequestWithBody(c.Server, did, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if c.RequestEditor != nil { + err = c.RequestEditor(ctx, req) + if err != nil { + return nil, err + } + } + return c.Client.Do(req) +} + +func (c *Client) UpdateDID(ctx context.Context, did string, body UpdateDIDJSONRequestBody) (*http.Response, error) { + req, err := NewUpdateDIDRequest(c.Server, did, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if c.RequestEditor != nil { + err = c.RequestEditor(ctx, req) + if err != nil { + return nil, err + } + } + return c.Client.Do(req) +} + +// NewCreateDIDRequest generates requests for CreateDID +func NewCreateDIDRequest(server string) (*http.Request, error) { + var err error + + queryUrl, err := url.Parse(server) + if err != nil { + return nil, err + } + + basePath := fmt.Sprintf("/internal/vdr/v1/did") + if basePath[0] == '/' { + basePath = basePath[1:] + } + + queryUrl, err = queryUrl.Parse(basePath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryUrl.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetDIDRequest generates requests for GetDID +func NewGetDIDRequest(server string, did string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParam("simple", false, "did", did) + if err != nil { + return nil, err + } + + queryUrl, err := url.Parse(server) + if err != nil { + return nil, err + } + + basePath := fmt.Sprintf("/internal/vdr/v1/did/%s", pathParam0) + if basePath[0] == '/' { + basePath = basePath[1:] + } + + queryUrl, err = queryUrl.Parse(basePath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryUrl.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateDIDRequest calls the generic UpdateDID builder with application/json body +func NewUpdateDIDRequest(server string, did string, body UpdateDIDJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateDIDRequestWithBody(server, did, "application/json", bodyReader) +} + +// NewUpdateDIDRequestWithBody generates requests for UpdateDID with any type of body +func NewUpdateDIDRequestWithBody(server string, did string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParam("simple", false, "did", did) + if err != nil { + return nil, err + } + + queryUrl, err := url.Parse(server) + if err != nil { + return nil, err + } + + basePath := fmt.Sprintf("/internal/vdr/v1/did/%s", pathParam0) + if basePath[0] == '/' { + basePath = basePath[1:] + } + + queryUrl, err = queryUrl.Parse(basePath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryUrl.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + return req, nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // CreateDID request + CreateDIDWithResponse(ctx context.Context) (*CreateDIDResponse, error) + + // GetDID request + GetDIDWithResponse(ctx context.Context, did string) (*GetDIDResponse, error) + + // UpdateDID request with any body + UpdateDIDWithBodyWithResponse(ctx context.Context, did string, contentType string, body io.Reader) (*UpdateDIDResponse, error) + + UpdateDIDWithResponse(ctx context.Context, did string, body UpdateDIDJSONRequestBody) (*UpdateDIDResponse, error) +} + +type CreateDIDResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r CreateDIDResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateDIDResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetDIDResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r GetDIDResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetDIDResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateDIDResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r UpdateDIDResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateDIDResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// CreateDIDWithResponse request returning *CreateDIDResponse +func (c *ClientWithResponses) CreateDIDWithResponse(ctx context.Context) (*CreateDIDResponse, error) { + rsp, err := c.CreateDID(ctx) + if err != nil { + return nil, err + } + return ParseCreateDIDResponse(rsp) +} + +// GetDIDWithResponse request returning *GetDIDResponse +func (c *ClientWithResponses) GetDIDWithResponse(ctx context.Context, did string) (*GetDIDResponse, error) { + rsp, err := c.GetDID(ctx, did) + if err != nil { + return nil, err + } + return ParseGetDIDResponse(rsp) +} + +// UpdateDIDWithBodyWithResponse request with arbitrary body returning *UpdateDIDResponse +func (c *ClientWithResponses) UpdateDIDWithBodyWithResponse(ctx context.Context, did string, contentType string, body io.Reader) (*UpdateDIDResponse, error) { + rsp, err := c.UpdateDIDWithBody(ctx, did, contentType, body) + if err != nil { + return nil, err + } + return ParseUpdateDIDResponse(rsp) +} + +func (c *ClientWithResponses) UpdateDIDWithResponse(ctx context.Context, did string, body UpdateDIDJSONRequestBody) (*UpdateDIDResponse, error) { + rsp, err := c.UpdateDID(ctx, did, body) + if err != nil { + return nil, err + } + return ParseUpdateDIDResponse(rsp) +} + +// ParseCreateDIDResponse parses an HTTP response from a CreateDIDWithResponse call +func ParseCreateDIDResponse(rsp *http.Response) (*CreateDIDResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &CreateDIDResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + } + + return response, nil +} + +// ParseGetDIDResponse parses an HTTP response from a GetDIDWithResponse call +func ParseGetDIDResponse(rsp *http.Response) (*GetDIDResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &GetDIDResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + } + + return response, nil +} + +// ParseUpdateDIDResponse parses an HTTP response from a UpdateDIDWithResponse call +func ParseUpdateDIDResponse(rsp *http.Response) (*UpdateDIDResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &UpdateDIDResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Creates a new Nuts DID + // (POST /internal/vdr/v1/did) + CreateDID(ctx echo.Context) error + // Resolves a Nuts DID Document + // (GET /internal/vdr/v1/did/{did}) + GetDID(ctx echo.Context, did string) error + // Updates a Nuts DID Document. + // (PUT /internal/vdr/v1/did/{did}) + UpdateDID(ctx echo.Context, did string) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// CreateDID converts echo context to params. +func (w *ServerInterfaceWrapper) CreateDID(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.CreateDID(ctx) + return err +} + +// GetDID converts echo context to params. +func (w *ServerInterfaceWrapper) GetDID(ctx echo.Context) error { + var err error + // ------------- Path parameter "did" ------------- + var did string + + err = runtime.BindStyledParameter("simple", false, "did", ctx.Param("did"), &did) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter did: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetDID(ctx, did) + return err +} + +// UpdateDID converts echo context to params. +func (w *ServerInterfaceWrapper) UpdateDID(ctx echo.Context) error { + var err error + // ------------- Path parameter "did" ------------- + var did string + + err = runtime.BindStyledParameter("simple", false, "did", ctx.Param("did"), &did) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter did: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.UpdateDID(ctx, did) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.POST(baseURL+"/internal/vdr/v1/did", wrapper.CreateDID) + router.GET(baseURL+"/internal/vdr/v1/did/:did", wrapper.GetDID) + router.PUT(baseURL+"/internal/vdr/v1/did/:did", wrapper.UpdateDID) + +} + diff --git a/vdr/api/v1/types.go b/vdr/api/v1/types.go new file mode 100644 index 0000000000..889adceb78 --- /dev/null +++ b/vdr/api/v1/types.go @@ -0,0 +1,27 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package v1 + +import ( + "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-node/vdr/types" +) + +// DIDDocument is an alias +type DIDDocument = did.Document + +// DIDDocumentMetadata is an alias +type DIDDocumentMetadata = types.DocumentMetadata diff --git a/vdr/config.go b/vdr/config.go new file mode 100644 index 0000000000..6297985d46 --- /dev/null +++ b/vdr/config.go @@ -0,0 +1,24 @@ +package vdr + +// ConfDataDir is the config name for specifiying the data location of the requiredFiles +const ConfDataDir = "datadir" + +// ConfClientTimeout is the time-out for the client in seconds (e.g. when using the CLI). +const ConfClientTimeout = "clientTimeout" + +// ModuleName contains the name of this module +const ModuleName = "Verifiable Data Registry" + +// Config holds the config for the VDR engine +type Config struct { + Datadir string + ClientTimeout int +} + +// DefaultConfig returns a fresh Config filled with default values +func DefaultConfig() Config { + return Config{ + Datadir: "./data", + ClientTimeout: 10, + } +} diff --git a/vdr/doc-creator.go b/vdr/doc-creator.go new file mode 100644 index 0000000000..39a7146fa0 --- /dev/null +++ b/vdr/doc-creator.go @@ -0,0 +1,117 @@ +package vdr + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/sha256" + "errors" + "fmt" + "net/url" + + "github.com/lestrrat-go/jwx/jwk" + "github.com/shengdoushi/base58" + + "github.com/nuts-foundation/go-did" + + nutsCrypto "github.com/nuts-foundation/nuts-node/crypto" +) + +// NutsDIDMethodName is the DID method name used by Nuts +const NutsDIDMethodName = "nuts" + +// NutsDocCreator implements the DocCreator interface and can create Nuts DID Documents. +type NutsDocCreator struct { + // keyCreator is used for getting a fresh key and use it to generate the Nuts DID + keyCreator nutsCrypto.KeyCreator +} + +func didKIDNamingFunc(pKey crypto.PublicKey) (string, error) { + ecPKey, ok := pKey.(*ecdsa.PublicKey) + if !ok { + return "", errors.New("could not generate kid: invalid key type") + } + + if ecPKey.Curve == nil { + return "", errors.New("could not generate kid: empty key curve") + } + + // according to RFC006: + // -------------------- + + // generate idString + pkBytes := elliptic.Marshal(ecPKey.Curve, ecPKey.X, ecPKey.Y) + pkHash := sha256.Sum256(pkBytes) + idString := base58.Encode(pkHash[:], base58.BitcoinAlphabet) + + // generate kid fragment + jwKey, err := jwk.New(pKey) + if err != nil { + return "", err + } + err = jwk.AssignKeyID(jwKey) + if err != nil { + return "", err + } + + // assemble + kid := &did.DID{} + kid.Method = NutsDIDMethodName + kid.ID = idString + kid.Fragment = jwKey.KeyID() + + return kid.String(), nil +} + +// Create creates a Nuts DID Document with a valid DID id based on a freshly generated keypair. +// The key is added to the verificationMethod list and referred to from the Authentication list +func (n NutsDocCreator) Create() (*did.Document, error) { + // First, generate a new keyPair with the correct kid + key, keyID, err := n.keyCreator.New(didKIDNamingFunc) + if err != nil { + return nil, fmt.Errorf("unable to build did: %w", err) + } + + // The Document DID can be generated from the keyID without the fragment: + didID, err := did.ParseDID(keyID) + didID.Fragment = "" + + verificationMethod, err := keyToVerificationMethod(key, keyID) + verificationMethod.Controller = *didID + + doc := &did.Document{ + Context: []did.URI{did.DIDContextV1URI()}, + ID: *didID, + Controller: []did.DID{*didID}, + VerificationMethod: []did.VerificationMethod{*verificationMethod}, + Authentication: []did.VerificationRelationship{{VerificationMethod: verificationMethod}}, + } + return doc, nil +} + +// jwkToVerificationMethod takes a jwk.Key and converts it to a DID VerificationMethod +func keyToVerificationMethod(key crypto.PublicKey, keyID string) (*did.VerificationMethod, error) { + // make use of the jwk helper functions + publicKeyJWK, err := jwk.New(key) + if err != nil { + return nil, err + } + err = publicKeyJWK.Set(jwk.KeyIDKey, keyID) + if err != nil { + return nil, err + } + publicKeyAsJWKAsMap, err := publicKeyJWK.AsMap(context.Background()) + if err != nil { + return nil, err + } + kid, err := url.Parse(publicKeyJWK.KeyID()) + if err != nil { + return nil, err + } + return &did.VerificationMethod{ + ID: did.URI{URL: *kid}, + Type: did.JsonWebKey2020, + PublicKeyJwk: publicKeyAsJWKAsMap, + }, nil +} diff --git a/vdr/doc-creator_test.go b/vdr/doc-creator_test.go new file mode 100644 index 0000000000..0a609af46d --- /dev/null +++ b/vdr/doc-creator_test.go @@ -0,0 +1,129 @@ +package vdr + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "testing" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" + "github.com/stretchr/testify/assert" + + nutsCrypto "github.com/nuts-foundation/nuts-node/crypto" +) + +// mockKeyCreator can create new keys based on a predefined key +type mockKeyCreator struct { + // jwkStr hold the predefined key in a json web key string + jwkStr string + t *testing.T +} + +// New uses a predefined ECDSA key and calls the namingFunc to get the kid +func (m *mockKeyCreator) New(namingFunc nutsCrypto.KIDNamingFunc) (crypto.PublicKey, string, error) { + rawKey, err := jwkToPublicKey(m.t, m.jwkStr) + if err != nil { + return nil, "", err + } + kid, err := namingFunc(rawKey) + if err != nil { + return nil, "", err + } + return rawKey, kid, nil +} + +var jwkString = `{"crv":"P-256","kid":"did:nuts:ARRW2e42qyVjQZiACk4Up3mzpshZdJBDBPWsuFQPcDiS#J9O6wvqtYOVwjc8JtZ4aodRdbPv_IKAjLkEq9uHlDdE","kty":"EC","x":"Qn6xbZtOYFoLO2qMEAczcau9uGGWwa1bT+7JmAVLtg4=","y":"d20dD0qlT+d1djVpAfrfsAfKOUxKwKkn1zqFSIuJ398="},"type":"JsonWebKey2020"}` + +func TestDocCreator_Create(t *testing.T) { + t.Run("ok", func(t *testing.T) { + kc := &mockKeyCreator{ + t: t, + jwkStr: jwkString, + } + sut := NutsDocCreator{keyCreator: kc} + t.Run("ok", func(t *testing.T) { + doc, err := sut.Create() + assert.NoError(t, err) + assert.NotNil(t, doc) + + assert.Equal(t, "did:nuts:ARRW2e42qyVjQZiACk4Up3mzpshZdJBDBPWsuFQPcDiS", doc.ID.String()) + + assert.Len(t, doc.Controller, 1) + assert.Equal(t, doc.ID.String(), doc.Controller[0].String()) + + assert.Len(t, doc.VerificationMethod, 1) + assert.Equal(t, "did:nuts:ARRW2e42qyVjQZiACk4Up3mzpshZdJBDBPWsuFQPcDiS#J9O6wvqtYOVwjc8JtZ4aodRdbPv_IKAjLkEq9uHlDdE", doc.VerificationMethod[0].ID.String()) + + assert.Len(t, doc.Authentication, 1) + assert.Equal(t, doc.Authentication[0].VerificationMethod, &doc.VerificationMethod[0]) + + assert.Empty(t, doc.AssertionMethod) + }) + }) +} + +func Test_didKidNamingFunc(t *testing.T) { + t.Run("ok", func(t *testing.T) { + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if assert.NoError(t, err) { + return + } + + keyID, err := didKIDNamingFunc(privateKey.PublicKey) + if !assert.NoError(t, err) { + return + } + assert.NotEmpty(t, keyID) + assert.Contains(t, keyID, "did:nuts") + }) + + t.Run("nok - wrong key type", func(t *testing.T) { + privateKey := rsa.PrivateKey{} + keyID, err := didKIDNamingFunc(privateKey.PublicKey) + if !assert.Error(t, err) { + return + } + assert.Equal(t, "could not generate kid: invalid key type", err.Error()) + assert.Empty(t, keyID) + + }) + + t.Run("nok - empty key", func(t *testing.T) { + pubKey := &ecdsa.PublicKey{} + keyID, err := didKIDNamingFunc(pubKey) + assert.Error(t, err) + assert.Equal(t, "could not generate kid: empty key curve", err.Error()) + assert.Empty(t, keyID) + }) +} + +func jwkToPublicKey(t *testing.T, jwkStr string) (crypto.PublicKey, error) { + t.Helper() + keySet, err := jwk.ParseString(jwkStr) + if !assert.NoError(t, err) { + return nil, err + } + key := keySet.Keys[0] + var rawKey crypto.PublicKey + if err = key.Raw(&rawKey); err != nil { + return nil, err + } + return rawKey, nil +} + +func Test_keyToVerificationMethod(t *testing.T) { + rawKey, err := jwkToPublicKey(t, jwkString) + if !assert.NoError(t, err) { + return + } + vm, err := keyToVerificationMethod(rawKey, "keyID") + assert.NoError(t, err) + assert.NotNil(t, vm) + + assert.Equal(t, "keyID", vm.ID.String()) + assert.Equal(t, "JsonWebKey2020", vm.Type) + assert.Equal(t, jwa.EllipticCurveAlgorithm("P-256"), vm.PublicKeyJwk["crv"]) +} \ No newline at end of file diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go new file mode 100644 index 0000000000..84e76774aa --- /dev/null +++ b/vdr/engine/engine.go @@ -0,0 +1,193 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package engine + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "strings" + "time" + + "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-node/core" + "github.com/nuts-foundation/nuts-node/vdr" + api "github.com/nuts-foundation/nuts-node/vdr/api/v1" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// NewVDREngine returns the core definition for the VDR +func NewVDREngine() *core.Engine { + r := vdr.Instance() + + return &core.Engine{ + Cmd: cmd(), + Runnable: r, + Configurable: r, + Diagnosable: r, + Config: &r.Config, + ConfigKey: "vdr", + FlagSet: flagSet(), + Name: vdr.ModuleName, + Routes: func(router core.EchoRouter) { + api.RegisterHandlers(router, &api.Wrapper{VDR: r}) + }, + } +} + +func flagSet() *pflag.FlagSet { + flagSet := pflag.NewFlagSet("vdr", pflag.ContinueOnError) + + defs := vdr.DefaultConfig() + flagSet.String(vdr.ConfDataDir, defs.Datadir, fmt.Sprintf("Location of data files, default: %s", defs.Datadir)) + flagSet.Int(vdr.ConfClientTimeout, defs.ClientTimeout, fmt.Sprintf("Time-out for the client in seconds (e.g. when using the CLI), default: %d", defs.ClientTimeout)) + + return flagSet +} + +func cmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "vdr", + Short: "Verifiable Data VDR commands", + } + + cmd.AddCommand(createCmd()) + + cmd.AddCommand(resolveCmd()) + + cmd.AddCommand(updateCmd()) + + return cmd +} + +func createCmd() *cobra.Command { + return &cobra.Command{ + Use: "create-did", + Short: "Registers a new DID", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + client := httpClient() + + doc, err := client.Create() + if err != nil { + return fmt.Errorf("unable to create new DID: %v", err) + } + + bytes, _ := json.MarshalIndent(doc, "", " ") + + cmd.Printf("Created DID document: %s\n", string(bytes)) + return nil + }, + } +} + +func updateCmd() *cobra.Command { + return &cobra.Command{ + Use: "update [DID] [hash] [file]", + Short: "Update a DID with the given DID document, this replaces the DID document. " + + "If no file is given, a pipe is assumed. The hash is needed to prevent concurrent updates.", + Args: cobra.RangeArgs(2, 3), + RunE: func(cmd *cobra.Command, args []string) error { + client := httpClient() + + id := args[0] + hash := args[1] + + var bytes []byte + var err error + if len(args) == 3 { + // read from file + bytes, err = ioutil.ReadFile(args[2]) + if err != nil { + return fmt.Errorf("failed to read file %s: %w", args[2], err) + } + } else { + // read from stdin + bytes, err = readFromStdin() + if err != nil { + return fmt.Errorf("failed to read from pipe: %w", err) + } + } + + // parse + var didDoc did.Document + if err = json.Unmarshal(bytes, &didDoc); err != nil { + return fmt.Errorf("failed to parse DID document: %w", err) + } + + if _, err = client.Update(id, hash, didDoc); err != nil { + return fmt.Errorf("failed to update DID document: %w", err) + } + + cmd.Println("DID document updated") + return nil + }, + } +} + +func resolveCmd() *cobra.Command { + return &cobra.Command{ + Use: "resolve [DID]", + Short: "Resolve a DID document based on its DID", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client := httpClient() + + doc, meta, err := client.Get(args[0]) + if err != nil { + return fmt.Errorf("failed to resolve DID document: %v", err) + } + + for _, o := range []interface{}{doc, meta} { + bytes, _ := json.MarshalIndent(o, "", " ") + cmd.Printf("%s\n", string(bytes)) + } + + return nil + }, + } +} + +func readFromStdin() ([]byte, error) { + fi, err := os.Stdin.Stat() + if err != nil { + return nil, err + } + if fi.Mode()&os.ModeNamedPipe == 0 { + return nil, errors.New("expected piped input") + } + return ioutil.ReadAll(bufio.NewReader(os.Stdin)) +} + +func httpClient() api.HTTPClient { + // TODO: allow for https + addr := core.NutsConfig().ServerAddress() + if !strings.HasPrefix(addr, "http") { + addr = "http://" + addr + } + return api.HTTPClient{ + ServerAddress: addr, + Timeout: 5 * time.Second, + } +} diff --git a/vdr/engine/engine_test.go b/vdr/engine/engine_test.go new file mode 100644 index 0000000000..b450b1fe70 --- /dev/null +++ b/vdr/engine/engine_test.go @@ -0,0 +1,225 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package engine + +import ( + "bytes" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/nuts-foundation/go-did" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + + http2 "github.com/nuts-foundation/nuts-node/test/http" + v1 "github.com/nuts-foundation/nuts-node/vdr/api/v1" + + core "github.com/nuts-foundation/nuts-node/core" +) + +func Test_flagSet(t *testing.T) { + assert.NotNil(t, flagSet()) +} + +func TestNewRegistryEngine(t *testing.T) { + // Register test instance singleton + t.Run("instance", func(t *testing.T) { + assert.NotNil(t, NewVDREngine()) + }) + + t.Run("configuration", func(t *testing.T) { + e := NewVDREngine() + cfg := core.NutsConfig() + cfg.RegisterFlags(e.Cmd, e) + assert.NoError(t, cfg.InjectIntoEngine(e)) + }) +} + +func TestEngine_Command(t *testing.T) { + core.NutsConfig().Load(&cobra.Command{}) + + createCmd := func(t *testing.T) *cobra.Command { + return NewVDREngine().Cmd + } + + exampleID, _ := did.ParseDID("did:nuts:Fx8kamg7Bom4gyEzmJc9t9QmWTkCwSxu3mrp3CbkehR7") + exampleDIDDocument := did.Document{ + ID: *exampleID, + Controller: []did.DID{*exampleID}, + } + + exampleDIDRsolution := v1.DIDResolutionResult{ + Document: exampleDIDDocument, + DocumentMetadata: v1.DIDDocumentMetadata{}, + } + + t.Run("create-did", func(t *testing.T) { + t.Run("ok - write to stdout", func(t *testing.T) { + cmd := createCmd(t) + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusOK, ResponseData: exampleDIDDocument}) + os.Setenv("NUTS_ADDRESS", s.URL) + core.NutsConfig().Load(cmd) + defer s.Close() + + buf := new(bytes.Buffer) + cmd.SetArgs([]string{"create-did"}) + cmd.SetOut(buf) + err := cmd.Execute() + + if !assert.NoError(t, err) { + return + } + assert.Contains(t, buf.String(), "Created DID document") + assert.Contains(t, buf.String(), "did:nuts:Fx8kamg7Bom4gyEzmJc9t9QmWTkCwSxu3mrp3CbkehR7") + }) + + t.Run("error - server error", func(t *testing.T) { + cmd := createCmd(t) + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusInternalServerError, ResponseData: "b00m!"}) + os.Setenv("NUTS_ADDRESS", s.URL) + core.NutsConfig().Load(cmd) + defer s.Close() + + buf := new(bytes.Buffer) + cmd.SetArgs([]string{"create-did"}) + cmd.SetOut(buf) + err := cmd.Execute() + + if !assert.Error(t, err) { + return + } + assert.Contains(t, buf.String(), "unable to create new DID") + assert.Contains(t, buf.String(), "b00m!") + }) + }) + + t.Run("resolve", func(t *testing.T) { + t.Run("ok - write to stdout", func(t *testing.T) { + cmd := createCmd(t) + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusOK, ResponseData: exampleDIDRsolution}) + os.Setenv("NUTS_ADDRESS", s.URL) + core.NutsConfig().Load(cmd) + defer s.Close() + + buf := new(bytes.Buffer) + cmd.SetArgs([]string{"resolve", "did"}) + cmd.SetOut(buf) + err := cmd.Execute() + + if !assert.NoError(t, err) { + return + } + assert.Contains(t, buf.String(), "did:nuts:Fx8kamg7Bom4gyEzmJc9t9QmWTkCwSxu3mrp3CbkehR7") + assert.Contains(t, buf.String(), "version") + }) + + t.Run("error - not found", func(t *testing.T) { + cmd := createCmd(t) + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusNotFound, ResponseData: "not found"}) + os.Setenv("NUTS_ADDRESS", s.URL) + core.NutsConfig().Load(cmd) + defer s.Close() + + buf := new(bytes.Buffer) + cmd.SetArgs([]string{"resolve", "did"}) + cmd.SetOut(buf) + err := cmd.Execute() + + if !assert.Error(t, err) { + return + } + assert.Contains(t, buf.String(), "failed to resolve DID document") + assert.Contains(t, buf.String(), "not found") + }) + }) + + t.Run("update", func(t *testing.T) { + t.Run("ok - write to stdout", func(t *testing.T) { + cmd := createCmd(t) + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusOK, ResponseData: exampleDIDDocument}) + os.Setenv("NUTS_ADDRESS", s.URL) + core.NutsConfig().Load(cmd) + defer s.Close() + + buf := new(bytes.Buffer) + cmd.SetArgs([]string{"update", "did", "hash", "../test/diddocument.json"}) + cmd.SetOut(buf) + err := cmd.Execute() + + if !assert.NoError(t, err) { + return + } + assert.Contains(t, buf.String(), "DID document updated") + }) + + t.Run("error - incorrect input", func(t *testing.T) { + cmd := createCmd(t) + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusOK, ResponseData: exampleDIDDocument}) + os.Setenv("NUTS_ADDRESS", s.URL) + core.NutsConfig().Load(cmd) + defer s.Close() + + buf := new(bytes.Buffer) + cmd.SetArgs([]string{"update", "did", "hash", "../test/syntax_error.json"}) + cmd.SetOut(buf) + err := cmd.Execute() + + if !assert.Error(t, err) { + return + } + assert.Contains(t, buf.String(), "failed to parse DID document") + }) + + t.Run("error - server error", func(t *testing.T) { + cmd := createCmd(t) + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusBadRequest, ResponseData: "invalid"}) + os.Setenv("NUTS_ADDRESS", s.URL) + core.NutsConfig().Load(cmd) + defer s.Close() + + buf := new(bytes.Buffer) + cmd.SetArgs([]string{"update", "did", "hash", "../test/diddocument.json"}) + cmd.SetOut(buf) + err := cmd.Execute() + + if !assert.Error(t, err) { + return + } + assert.Contains(t, buf.String(), "failed to update DID document") + assert.Contains(t, buf.String(), "invalid") + }) + }) +} + +func Test_httpClient(t *testing.T) { + t.Run("address has http prefix", func(t *testing.T) { + os.Setenv("NUTS_ADDRESS", "https://localhost") + core.NutsConfig().Load(&cobra.Command{}) + client := httpClient() + assert.Equal(t, "https://localhost", client.ServerAddress) + }) + t.Run("address has no http prefix", func(t *testing.T) { + os.Setenv("NUTS_ADDRESS", "localhost") + core.NutsConfig().Load(&cobra.Command{}) + client := httpClient() + assert.Equal(t, "http://localhost", client.ServerAddress) + }) +} \ No newline at end of file diff --git a/vdr/logging/log.go b/vdr/logging/log.go new file mode 100644 index 0000000000..09723c4a39 --- /dev/null +++ b/vdr/logging/log.go @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 Nuts community + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package logging + +import ( + "github.com/sirupsen/logrus" +) + +var _logger = logrus.StandardLogger().WithField("module", "VDR") + +// Log returns a logger which should be used for logging in this engine. It adds fields so +// log entries from this engine can be recognized as such. +func Log() *logrus.Entry { + return _logger +} diff --git a/vdr/logging/log_test.go b/vdr/logging/log_test.go new file mode 100644 index 0000000000..bb659af412 --- /dev/null +++ b/vdr/logging/log_test.go @@ -0,0 +1,15 @@ +package logging + +import ( + "github.com/magiconair/properties/assert" + "testing" +) + +func TestLog(t *testing.T) { + t.Run("can log", func(t *testing.T) { + Log().Info("Works") + }) + t.Run("has correct module field", func(t *testing.T) { + assert.Equal(t, Log().Data["module"], "VDR") + }) +} diff --git a/vdr/network/ambassador.go b/vdr/network/ambassador.go new file mode 100644 index 0000000000..673b650724 --- /dev/null +++ b/vdr/network/ambassador.go @@ -0,0 +1,83 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package network + +import ( + "bytes" + + network "github.com/nuts-foundation/nuts-network/pkg" + "github.com/nuts-foundation/nuts-network/pkg/model" + + "github.com/nuts-foundation/nuts-node/vdr/logging" +) + +const documentType = "nuts.registry-event" + +// Ambassador acts as integration point between the registry and network by sending registry events to the +// network and (later on) process notifications of new documents on the network that might be of interest to the registry. +type Ambassador interface { + // Start instructs the ambassador to start receiving events from the network. + Start() +} + +type ambassador struct { + networkClient network.NetworkClient +} + +// NewAmbassador creates a new Ambassador. Don't forget to call RegisterEventHandlers afterwards. +func NewAmbassador(networkClient network.NetworkClient) Ambassador { + instance := &ambassador{ + networkClient: networkClient, + } + return instance +} + +// Start instructs the ambassador to start receiving events from the network. +func (n *ambassador) Start() { + queue := n.networkClient.Subscribe(documentType) + go func() { + for { + document := queue.Get() + if document == nil { + return + } + n.processDocument(document) + } + }() +} + +func (n *ambassador) sendEventToNetwork() { + logging.Log().Infof("Event published on network") +} + +func (n *ambassador) processDocument(document *model.Document) { + logging.Log().Infof("Received event through Nuts Network: %s", document.Hash) + reader, err := n.networkClient.GetDocumentContents(document.Hash) + if err != nil { + logging.Log().Errorf("Unable to retrieve document from Nuts Network (hash=%s): %v", document.Hash, err) + return + } + buf := new(bytes.Buffer) + if _, err = buf.ReadFrom(reader); err != nil { + logging.Log().Errorf("Unable read document data from Nuts Network (hash=%s): %v", document.Hash, err) + return + } + // todo process +} diff --git a/vdr/network/mock.go b/vdr/network/mock.go new file mode 100644 index 0000000000..0a565ffd39 --- /dev/null +++ b/vdr/network/mock.go @@ -0,0 +1,45 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: pkg/network/ambassador.go + +// Package network is a generated GoMock package. +package network + +import ( + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockAmbassador is a mock of Ambassador interface +type MockAmbassador struct { + ctrl *gomock.Controller + recorder *MockAmbassadorMockRecorder +} + +// MockAmbassadorMockRecorder is the mock recorder for MockAmbassador +type MockAmbassadorMockRecorder struct { + mock *MockAmbassador +} + +// NewMockAmbassador creates a new mock instance +func NewMockAmbassador(ctrl *gomock.Controller) *MockAmbassador { + mock := &MockAmbassador{ctrl: ctrl} + mock.recorder = &MockAmbassadorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockAmbassador) EXPECT() *MockAmbassadorMockRecorder { + return m.recorder +} + +// Start mocks base method +func (m *MockAmbassador) Start() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Start") +} + +// Start indicates an expected call of Start +func (mr *MockAmbassadorMockRecorder) Start() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockAmbassador)(nil).Start)) +} diff --git a/vdr/store/memory.go b/vdr/store/memory.go new file mode 100644 index 0000000000..8735bcd907 --- /dev/null +++ b/vdr/store/memory.go @@ -0,0 +1,214 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package store + +import ( + "encoding/json" + "sync" + + "github.com/nuts-foundation/go-did" + + "github.com/nuts-foundation/nuts-node/crypto/hash" + "github.com/nuts-foundation/nuts-node/vdr/types" +) + +// NewMemoryStore initializes a new in-memory store +// All actions on the store are thread safe. +func NewMemoryStore() types.Store { + return &memory{ + store: map[string]versionedEntryList{}, + mutex: sync.Mutex{}, + } +} + +type versionedEntryList []*memoryEntry + +// filterFunc returns true if value must be kept +type filterFunc func(e memoryEntry) bool + +func (list versionedEntryList) filter(f filterFunc) versionedEntryList { + var vel versionedEntryList + for _, entry := range list { + if f(*entry) { + vel = append(vel, entry) + } + } + return vel +} + +func (list versionedEntryList) last() (*memoryEntry, error) { + if len(list) == 0 { + return nil, types.ErrNotFound + } + return list[len(list)-1], nil +} + +type memory struct { + store map[string]versionedEntryList + mutex sync.Mutex +} + +type memoryEntry struct { + document did.Document + metadata types.DocumentMetadata + next *memoryEntry +} + +func (me memoryEntry) isDeactivated() bool { + return len(me.document.Controller) == 0 && len(me.document.Authentication) == 0 +} + +// Resolve implements the DocResolver. +// Resolves a DID document and returns a deep copy of the data in memory. +func (m *memory) Resolve(id did.DID, metadata *types.ResolveMetadata) (*did.Document, *types.DocumentMetadata, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + entries, ok := m.store[id.String()] + if !ok { + return nil, nil, types.ErrNotFound + } + + if metadata != nil { + // filter on hash + if metadata.Hash != nil { + entries = entries.filter(func(e memoryEntry) bool { + return metadata.Hash.Equals(e.metadata.Hash) + }) + } + + // filter on isDeactivated + if !metadata.AllowDeactivated { + entries = entries.filter(func(e memoryEntry) bool { + return !e.isDeactivated() + }) + } + + // filter on time + if metadata.ResolveTime != nil { + entries = entries.filter(timeSelectionFilter(*metadata)) + } + } + + entry, err := entries.last() + if err != nil { + return nil, nil, err + } + + // return a deep copy + doc := &did.Document{} + docMetadata := &types.DocumentMetadata{} + + docJSON, err := json.Marshal(entry.document) + if err != nil { + return nil, nil, err + } + if err = json.Unmarshal(docJSON, doc); err != nil { + return nil, nil, err + } + + metadataJSON, err := json.Marshal(entry.metadata) + if err != nil { + return nil, nil, err + } + if err = json.Unmarshal(metadataJSON, docMetadata); err != nil { + return nil, nil, err + } + + return doc, docMetadata, nil +} + +// timeSelectionFilter checks if an entry is after the created, after the updated field if present but before the updated field of the next entry +func timeSelectionFilter(metadata types.ResolveMetadata) filterFunc { + return func(e memoryEntry) bool { + if e.metadata.Created.After(*metadata.ResolveTime) { + return false + } + + if e.metadata.Updated != nil { + if e.metadata.Updated.After(*metadata.ResolveTime) { + // this specific version is created later + return false + } + } + + if e.next != nil { + // a next must always have an updated field + // the next version is created later, indicating this version is valid + return e.next.metadata.Updated.After(*metadata.ResolveTime) + } + + // last record in line + return true + } +} + +// Write implements the DocWriteWriter interface and writes a DIDDocument with the provided metadata to the memory store. +func (m *memory) Write(document did.Document, metadata types.DocumentMetadata) error { + m.mutex.Lock() + defer m.mutex.Unlock() + + if _, ok := m.store[document.ID.String()]; ok { + return types.ErrDIDAlreadyExists + } + + m.store[document.ID.String()] = versionedEntryList{ + &memoryEntry{ + document: document, + metadata: metadata, + }, + } + + return nil +} + +// Update implements the DocUpdater interface. +// It updates existing DIDDocument in the memory store with provided document and metadata. +// It does not check if the timestamp in the metadata make sense or if the metadata.hash matches the hash +// of the next version. The version field is also not checked. +func (m *memory) Update(id did.DID, current hash.SHA256Hash, next did.Document, metadata *types.DocumentMetadata) error { + m.mutex.Lock() + defer m.mutex.Unlock() + + entries, ok := m.store[id.String()] + if !ok { + return types.ErrNotFound + } + + // latest version is to be updated + entry, _ := entries.last() + + if entry.isDeactivated() { + return types.ErrDeactivated + } + + // hashes must match + if !current.Equals(entry.metadata.Hash) { + return types.ErrUpdateOnOutdatedData + } + + newEntry := &memoryEntry{ + document: next, + metadata: *metadata, + } + + // update next in last document + entry.next = newEntry + + m.store[id.String()] = append(entries, newEntry) + + return nil +} diff --git a/vdr/store/memory_test.go b/vdr/store/memory_test.go new file mode 100644 index 0000000000..ff8fc56ada --- /dev/null +++ b/vdr/store/memory_test.go @@ -0,0 +1,228 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package store + +import ( + "testing" + "time" + + "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-node/crypto/hash" + "github.com/nuts-foundation/nuts-node/vdr/types" + "github.com/stretchr/testify/assert" +) + +func TestMemory_Write(t *testing.T) { + store := NewMemoryStore() + did1, _ := did.ParseDID("did:nuts:1") + doc := did.Document{ + ID: *did1, + } + meta := types.DocumentMetadata{} + + err := store.Write(doc, meta) + + t.Run("returns no error on successful write", func(t *testing.T) { + assert.NoError(t, err) + }) + + t.Run("returns error when already exist", func(t *testing.T) { + err := store.Write(doc, meta) + if !assert.Error(t, err) { + return + } + assert.Equal(t, types.ErrDIDAlreadyExists, err) + }) +} + +func TestMemory_Resolve(t *testing.T) { + store := NewMemoryStore() + did1, _ := did.ParseDID("did:nuts:1") + doc := did.Document{ + ID: *did1, + Controller: []did.DID{*did1}, + } + + //upd := time.Now().Add(time.Hour * 24) + h, _ := hash.ParseHex("452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620") + meta := types.DocumentMetadata{ + Created: time.Now().Add(time.Hour * -24), + Hash: h, + } + + _ = store.Write(doc, meta) + + t.Run("returns ErrNotFound on unknown did", func(t *testing.T) { + did2, _ := did.ParseDID("did:nuts:2") + _, _, err := store.Resolve(*did2, nil) + assert.Equal(t, types.ErrNotFound, err) + }) + + t.Run("returns document without resolve metadata", func(t *testing.T) { + d, m, err := store.Resolve(*did1, nil) + if !assert.NoError(t, err) { + return + } + assert.NotNil(t, d) + assert.NotNil(t, m) + }) + + t.Run("returns document with resolve metadata - selection on date", func(t *testing.T) { + now := time.Now() + d, m, err := store.Resolve(*did1, &types.ResolveMetadata{ + ResolveTime: &now, + }) + if !assert.NoError(t, err) { + return + } + assert.NotNil(t, d) + assert.NotNil(t, m) + }) + + t.Run("returns no document with resolve metadata - selection on date", func(t *testing.T) { + before := time.Now().Add(time.Hour * -48) + _, _, err := store.Resolve(*did1, &types.ResolveMetadata{ + ResolveTime: &before, + }) + assert.Equal(t, types.ErrNotFound, err) + }) + + t.Run("returns document with resolve metadata - selection on hash", func(t *testing.T) { + d, m, err := store.Resolve(*did1, &types.ResolveMetadata{ + Hash: &h, + }) + if !assert.NoError(t, err) { + return + } + assert.NotNil(t, d) + assert.NotNil(t, m) + }) +} + +func TestTimeSelectionFilter(t *testing.T) { + earlier := time.Now().Add(time.Hour * -24) + now := time.Now() + later := time.Now().Add(time.Hour * 24) + + metadata := types.ResolveMetadata{ + ResolveTime: &now, + } + f := timeSelectionFilter(metadata) + + t.Run("returns false when created later", func(t *testing.T) { + entry := memoryEntry{ + metadata: types.DocumentMetadata{ + Created: later, + }, + } + assert.False(t, f(entry)) + }) + + t.Run("returns true when created earlier", func(t *testing.T) { + entry := memoryEntry{ + metadata: types.DocumentMetadata{ + Created: earlier, + }, + } + assert.True(t, f(entry)) + }) + + t.Run("returns false when next document was updated earlier", func(t *testing.T) { + entry := memoryEntry{ + metadata: types.DocumentMetadata{ + Created: earlier, + Updated: &earlier, + }, + next: &memoryEntry{ + metadata: types.DocumentMetadata{ + Created: earlier, + Updated: &earlier, + }, + }, + } + assert.False(t, f(entry)) + }) + + t.Run("returns false when document was updated later", func(t *testing.T) { + entry := memoryEntry{ + metadata: types.DocumentMetadata{ + Created: earlier, + Updated: &later, + }, + } + assert.False(t, f(entry)) + }) +} + +func TestMemory_Update(t *testing.T) { + store := NewMemoryStore() + did1, _ := did.ParseDID("did:nuts:1") + doc := did.Document{ + ID: *did1, + Controller: []did.DID{*did1}, + } + h, _ := hash.ParseHex("452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620") + meta := types.DocumentMetadata{ + Hash: h, + } + + _ = store.Write(doc, meta) + + t.Run("returns no error on success", func(t *testing.T) { + err := store.Update(*did1, h, doc, &meta) + assert.NoError(t, err) + }) + + t.Run("updates the previous record", func(t *testing.T) { + later := time.Now().Add(time.Hour * 24) + meta = types.DocumentMetadata{ + Hash: h, + Created: time.Now(), + Updated: &later, + } + err := store.Update(*did1, h, doc, &meta) + assert.NoError(t, err) + + s := store.(*memory) + assert.NotNil(t, s.store[did1.String()][0].next) + }) + + t.Run("returns error when DID document doesn't exist", func(t *testing.T) { + did1, _ := did.ParseDID("did:nuts:2") + err := store.Update(*did1, h, doc, &meta) + assert.Equal(t, types.ErrNotFound, err) + }) + + t.Run("returns error when hashes don't match", func(t *testing.T) { + h, _ := hash.ParseHex("452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4621") + err := store.Update(*did1, h, doc, &meta) + assert.Equal(t, types.ErrUpdateOnOutdatedData, err) + }) + + t.Run("returns error when DID Document is deactivated", func(t *testing.T) { + did1, _ := did.ParseDID("did:nuts:2") + doc := did.Document{ + ID: *did1, + } + err := store.Write(doc, meta) + if !assert.NoError(t, err) { + return + } + + err = store.Update(*did1, h, doc, &meta) + assert.Equal(t, types.ErrDeactivated, err) + }) +} diff --git a/vdr/test/diddocument.json b/vdr/test/diddocument.json new file mode 100644 index 0000000000..1b6d0dfd44 --- /dev/null +++ b/vdr/test/diddocument.json @@ -0,0 +1,19 @@ +{ + "authentication": ["did:nuts:Fx8kamg7Bom4gyEzmJc9t9QmWTkCwSxu3mrp3CbkehR7#aQgahRFAhdStcQyD6R25fFYslo4JZXuqYUySTtXB_Lo"], + "context": "https://www.w3.org/ns/did/v1", + "id": "did:nuts:Fx8kamg7Bom4gyEzmJc9t9QmWTkCwSxu3mrp3CbkehR7", + "verificationMethod": [ + { + "controller": "did:nuts:Fx8kamg7Bom4gyEzmJc9t9QmWTkCwSxu3mrp3CbkehR7", + "id": "did:nuts:Fx8kamg7Bom4gyEzmJc9t9QmWTkCwSxu3mrp3CbkehR7#aQgahRFAhdStcQyD6R25fFYslo4JZXuqYUySTtXB_Lo", + "publicKeyJwk": { + "crv": "P-256", + "kid": "did:nuts:Fx8kamg7Bom4gyEzmJc9t9QmWTkCwSxu3mrp3CbkehR7#aQgahRFAhdStcQyD6R25fFYslo4JZXuqYUySTtXB_Lo", + "kty": "EC", + "x": "iCpv6AGDpdYVZjniwklkL8A4DGNhBK/DngbpdDjjBlo=", + "y": "27/qwElvfxhXtG2TDSO5LwReuFRwR+qydBdpQqu6H5M=" + }, + "type": "JsonWebKey2020" + } + ] +} \ No newline at end of file diff --git a/vdr/test/syntax_error.json b/vdr/test/syntax_error.json new file mode 100644 index 0000000000..ff30235f07 --- /dev/null +++ b/vdr/test/syntax_error.json @@ -0,0 +1 @@ +} \ No newline at end of file diff --git a/vdr/types/common.go b/vdr/types/common.go new file mode 100644 index 0000000000..830c9a4d5b --- /dev/null +++ b/vdr/types/common.go @@ -0,0 +1,64 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package types + +import ( + "errors" + "time" + + "github.com/nuts-foundation/nuts-node/crypto/hash" +) + +// ErrUpdateOnOutdatedData is returned when a concurrent update is done on a DID document. +var ErrUpdateOnOutdatedData = errors.New("could not update outdated document") + +// ErrInvalidDID The DID supplied to the DID resolution function does not conform to valid syntax. +var ErrInvalidDID = errors.New("invalid did syntax") + +// ErrNotFound The DID resolver was unable to find the DID document resulting from this resolution request. +var ErrNotFound = errors.New("unable to find the did document") + +// ErrDeactivated The DID supplied to the DID resolution function has been deactivated. +var ErrDeactivated = errors.New("the document has been deactivated") + +// ErrDIDAlreadyExists is returned when a DID already exists. +var ErrDIDAlreadyExists = errors.New("did document already exists in the store") + +// DocumentMetadata holds the metadata of a DID document +type DocumentMetadata struct { + Created time.Time `json:"created"` + Updated *time.Time `json:"updated,omitempty"` + // Version contains the semantic version of the DID document. + Version int `json:"version"` + // OriginJWSHash contains the hash of the JWS envelope of the first version of the DID document. + OriginJWSHash hash.SHA256Hash `json:"originJwsHash"` + // Hash of DID document bytes. Is equal to payloadHash in network layer. + Hash hash.SHA256Hash `json:"hash"` +} + +// ResolveMetadata contains metadata for the resolver. +type ResolveMetadata struct { + // Resolve the version which is valid at this time + ResolveTime *time.Time + // if provided, use the version which matches this exact hash + Hash *hash.SHA256Hash + // Allow DIDs which are deactivated + AllowDeactivated bool +} diff --git a/vdr/types/interface.go b/vdr/types/interface.go new file mode 100644 index 0000000000..a16bfb2ebf --- /dev/null +++ b/vdr/types/interface.go @@ -0,0 +1,82 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package types + +import ( + "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-node/crypto/hash" +) + +// DocResolver is the interface that groups all the DID Document read methods +type DocResolver interface { + // Resolve returns a DID Document for the provided DID. + // If metadata is not provided the latest version is returned. + // If metadata is provided then the result is filtered or scoped on that metadata. + // It returns ErrNotFound if there are corresponding DID documents or when the DID Documents are disjoint with the provided ResolveMetadata + Resolve(id did.DID, metadata *ResolveMetadata) (*did.Document, *DocumentMetadata, error) +} + +// DocCreator is the interface that wraps the Create method +type DocCreator interface { + // Create creates a new DID document and returns it. + // The ID in the provided DID document will be ignored and a new one will be generated. + // If something goes wrong an error is returned. + // Implementors should generate private key and store it in a secure backend + Create() (*did.Document, error) +} + +// DocWriter is the interface that groups al the DID Document write methods +type DocWriter interface { + // Write writes a DID Document. + // Returns ErrDIDAlreadyExists when DID already exists + // When a document already exists, the Update should be used instead + Write(document did.Document, metadata DocumentMetadata) error +} + +// DocUpdater is the interface that defines functions that alter the state of a DID document +type DocUpdater interface { + // Update replaces the DID document identified by DID with the nextVersion + // To prevent updating stale data a hash of the current version should be provided. + // If the given hash does not represents the current version, a ErrUpdateOnOutdatedData is returned + // If the DID Document is not found or not local a ErrNotFound is returned + // If the DID Document is not active a ErrDeactivated is returned + Update(id did.DID, current hash.SHA256Hash, next did.Document, metadata *DocumentMetadata) error +} + +// DocDeactivator is the interface that defines functions to deactivate DID Docs +// Deactivation will be done in such a way that a DID doc cannot be used / activated anymore. +type DocDeactivator interface { + // To prevent updating stale data a hash of the current version should be provided. + // If the given hash does not represents the current version, a ErrUpdateOnOutdatedData is returned + // If the DID Document is not found or not local a ErrNotFound is returned + // If the DID Document is not active a ErrDeactivated is returned + Deactivate(id did.DID, current hash.SHA256Hash) +} + +// Store is the interface that groups all low level VDR DID storage operations. +type Store interface { + DocResolver + DocWriter + DocUpdater +} + +// VDR defines the public end facing methods for the Verifiable Data Registry. +type VDR interface { + DocResolver + DocCreator + DocUpdater + DocDeactivator +} diff --git a/vdr/types/mock.go b/vdr/types/mock.go new file mode 100644 index 0000000000..a1d7c3dd6c --- /dev/null +++ b/vdr/types/mock.go @@ -0,0 +1,345 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: vdr/types/interface.go + +// Package types is a generated GoMock package. +package types + +import ( + gomock "github.com/golang/mock/gomock" + go_did "github.com/nuts-foundation/go-did" + hash "github.com/nuts-foundation/nuts-node/crypto/hash" + reflect "reflect" +) + +// MockDocResolver is a mock of DocResolver interface +type MockDocResolver struct { + ctrl *gomock.Controller + recorder *MockDocResolverMockRecorder +} + +// MockDocResolverMockRecorder is the mock recorder for MockDocResolver +type MockDocResolverMockRecorder struct { + mock *MockDocResolver +} + +// NewMockDocResolver creates a new mock instance +func NewMockDocResolver(ctrl *gomock.Controller) *MockDocResolver { + mock := &MockDocResolver{ctrl: ctrl} + mock.recorder = &MockDocResolverMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockDocResolver) EXPECT() *MockDocResolverMockRecorder { + return m.recorder +} + +// Resolve mocks base method +func (m *MockDocResolver) Resolve(id go_did.DID, metadata *ResolveMetadata) (*go_did.Document, *DocumentMetadata, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Resolve", id, metadata) + ret0, _ := ret[0].(*go_did.Document) + ret1, _ := ret[1].(*DocumentMetadata) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Resolve indicates an expected call of Resolve +func (mr *MockDocResolverMockRecorder) Resolve(id, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resolve", reflect.TypeOf((*MockDocResolver)(nil).Resolve), id, metadata) +} + +// MockDocCreator is a mock of DocCreator interface +type MockDocCreator struct { + ctrl *gomock.Controller + recorder *MockDocCreatorMockRecorder +} + +// MockDocCreatorMockRecorder is the mock recorder for MockDocCreator +type MockDocCreatorMockRecorder struct { + mock *MockDocCreator +} + +// NewMockDocCreator creates a new mock instance +func NewMockDocCreator(ctrl *gomock.Controller) *MockDocCreator { + mock := &MockDocCreator{ctrl: ctrl} + mock.recorder = &MockDocCreatorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockDocCreator) EXPECT() *MockDocCreatorMockRecorder { + return m.recorder +} + +// Create mocks base method +func (m *MockDocCreator) Create() (*go_did.Document, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create") + ret0, _ := ret[0].(*go_did.Document) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create +func (mr *MockDocCreatorMockRecorder) Create() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockDocCreator)(nil).Create)) +} + +// MockDocWriter is a mock of DocWriter interface +type MockDocWriter struct { + ctrl *gomock.Controller + recorder *MockDocWriterMockRecorder +} + +// MockDocWriterMockRecorder is the mock recorder for MockDocWriter +type MockDocWriterMockRecorder struct { + mock *MockDocWriter +} + +// NewMockDocWriter creates a new mock instance +func NewMockDocWriter(ctrl *gomock.Controller) *MockDocWriter { + mock := &MockDocWriter{ctrl: ctrl} + mock.recorder = &MockDocWriterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockDocWriter) EXPECT() *MockDocWriterMockRecorder { + return m.recorder +} + +// Write mocks base method +func (m *MockDocWriter) Write(document go_did.Document, metadata DocumentMetadata) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", document, metadata) + ret0, _ := ret[0].(error) + return ret0 +} + +// Write indicates an expected call of Write +func (mr *MockDocWriterMockRecorder) Write(document, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockDocWriter)(nil).Write), document, metadata) +} + +// MockDocUpdater is a mock of DocUpdater interface +type MockDocUpdater struct { + ctrl *gomock.Controller + recorder *MockDocUpdaterMockRecorder +} + +// MockDocUpdaterMockRecorder is the mock recorder for MockDocUpdater +type MockDocUpdaterMockRecorder struct { + mock *MockDocUpdater +} + +// NewMockDocUpdater creates a new mock instance +func NewMockDocUpdater(ctrl *gomock.Controller) *MockDocUpdater { + mock := &MockDocUpdater{ctrl: ctrl} + mock.recorder = &MockDocUpdaterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockDocUpdater) EXPECT() *MockDocUpdaterMockRecorder { + return m.recorder +} + +// Update mocks base method +func (m *MockDocUpdater) Update(id go_did.DID, current hash.SHA256Hash, next go_did.Document, metadata *DocumentMetadata) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", id, current, next, metadata) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update +func (mr *MockDocUpdaterMockRecorder) Update(id, current, next, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDocUpdater)(nil).Update), id, current, next, metadata) +} + +// MockDocDeactivator is a mock of DocDeactivator interface +type MockDocDeactivator struct { + ctrl *gomock.Controller + recorder *MockDocDeactivatorMockRecorder +} + +// MockDocDeactivatorMockRecorder is the mock recorder for MockDocDeactivator +type MockDocDeactivatorMockRecorder struct { + mock *MockDocDeactivator +} + +// NewMockDocDeactivator creates a new mock instance +func NewMockDocDeactivator(ctrl *gomock.Controller) *MockDocDeactivator { + mock := &MockDocDeactivator{ctrl: ctrl} + mock.recorder = &MockDocDeactivatorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockDocDeactivator) EXPECT() *MockDocDeactivatorMockRecorder { + return m.recorder +} + +// Deactivate mocks base method +func (m *MockDocDeactivator) Deactivate(id go_did.DID, current hash.SHA256Hash) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Deactivate", id, current) +} + +// Deactivate indicates an expected call of Deactivate +func (mr *MockDocDeactivatorMockRecorder) Deactivate(id, current interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deactivate", reflect.TypeOf((*MockDocDeactivator)(nil).Deactivate), id, current) +} + +// MockStore is a mock of Store interface +type MockStore struct { + ctrl *gomock.Controller + recorder *MockStoreMockRecorder +} + +// MockStoreMockRecorder is the mock recorder for MockStore +type MockStoreMockRecorder struct { + mock *MockStore +} + +// NewMockStore creates a new mock instance +func NewMockStore(ctrl *gomock.Controller) *MockStore { + mock := &MockStore{ctrl: ctrl} + mock.recorder = &MockStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockStore) EXPECT() *MockStoreMockRecorder { + return m.recorder +} + +// Resolve mocks base method +func (m *MockStore) Resolve(id go_did.DID, metadata *ResolveMetadata) (*go_did.Document, *DocumentMetadata, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Resolve", id, metadata) + ret0, _ := ret[0].(*go_did.Document) + ret1, _ := ret[1].(*DocumentMetadata) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Resolve indicates an expected call of Resolve +func (mr *MockStoreMockRecorder) Resolve(id, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resolve", reflect.TypeOf((*MockStore)(nil).Resolve), id, metadata) +} + +// Write mocks base method +func (m *MockStore) Write(document go_did.Document, metadata DocumentMetadata) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", document, metadata) + ret0, _ := ret[0].(error) + return ret0 +} + +// Write indicates an expected call of Write +func (mr *MockStoreMockRecorder) Write(document, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockStore)(nil).Write), document, metadata) +} + +// Update mocks base method +func (m *MockStore) Update(id go_did.DID, current hash.SHA256Hash, next go_did.Document, metadata *DocumentMetadata) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", id, current, next, metadata) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update +func (mr *MockStoreMockRecorder) Update(id, current, next, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockStore)(nil).Update), id, current, next, metadata) +} + +// MockVDR is a mock of VDR interface +type MockVDR struct { + ctrl *gomock.Controller + recorder *MockVDRMockRecorder +} + +// MockVDRMockRecorder is the mock recorder for MockVDR +type MockVDRMockRecorder struct { + mock *MockVDR +} + +// NewMockVDR creates a new mock instance +func NewMockVDR(ctrl *gomock.Controller) *MockVDR { + mock := &MockVDR{ctrl: ctrl} + mock.recorder = &MockVDRMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockVDR) EXPECT() *MockVDRMockRecorder { + return m.recorder +} + +// Resolve mocks base method +func (m *MockVDR) Resolve(id go_did.DID, metadata *ResolveMetadata) (*go_did.Document, *DocumentMetadata, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Resolve", id, metadata) + ret0, _ := ret[0].(*go_did.Document) + ret1, _ := ret[1].(*DocumentMetadata) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Resolve indicates an expected call of Resolve +func (mr *MockVDRMockRecorder) Resolve(id, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resolve", reflect.TypeOf((*MockVDR)(nil).Resolve), id, metadata) +} + +// Create mocks base method +func (m *MockVDR) Create() (*go_did.Document, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create") + ret0, _ := ret[0].(*go_did.Document) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create +func (mr *MockVDRMockRecorder) Create() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockVDR)(nil).Create)) +} + +// Update mocks base method +func (m *MockVDR) Update(id go_did.DID, current hash.SHA256Hash, next go_did.Document, metadata *DocumentMetadata) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", id, current, next, metadata) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update +func (mr *MockVDRMockRecorder) Update(id, current, next, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockVDR)(nil).Update), id, current, next, metadata) +} + +// Deactivate mocks base method +func (m *MockVDR) Deactivate(id go_did.DID, current hash.SHA256Hash) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Deactivate", id, current) +} + +// Deactivate indicates an expected call of Deactivate +func (mr *MockVDRMockRecorder) Deactivate(id, current interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deactivate", reflect.TypeOf((*MockVDR)(nil).Deactivate), id, current) +} diff --git a/vdr/vdr.go b/vdr/vdr.go new file mode 100644 index 0000000000..53e4407dbc --- /dev/null +++ b/vdr/vdr.go @@ -0,0 +1,157 @@ +/* + * Nuts node + * Copyright (C) 2021 Nuts community + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +// Package vdr contains a verifiable data registry to the w3c specification +// and provides primitives for storing and working with Nuts DID based identities. +// It provides an easy to work with web api and a command line interface. +// It provides underlying storage back ends to store, update and search for Nuts identities. +package vdr + +import ( + "fmt" + "sync" + "time" + + "github.com/nuts-foundation/go-did" + "github.com/sirupsen/logrus" + + "github.com/nuts-foundation/nuts-node/vdr/store" + "github.com/nuts-foundation/nuts-node/vdr/types" + + "github.com/nuts-foundation/nuts-node/vdr/logging" + + "github.com/nuts-foundation/nuts-network/pkg" + + "github.com/nuts-foundation/nuts-node/vdr/network" + + networkClient "github.com/nuts-foundation/nuts-network/client" + networkPkg "github.com/nuts-foundation/nuts-network/pkg" + + "github.com/nuts-foundation/nuts-node/core" + "github.com/nuts-foundation/nuts-node/crypto" + "github.com/nuts-foundation/nuts-node/crypto/hash" +) + +// VDR stands for the Nuts Verifiable Data Registry. It is the public entrypoint to work with W3C DID documents. +// It connects the Resolve, Create and Update DID methods to the Network, and receives events back from the network which are processed in the store. +// It is also a Runnable, Diagnosable and Configurable Nuts Engine. +type VDR struct { + Config Config + store types.Store + network networkPkg.NetworkClient + OnChange func(registry *VDR) + networkAmbassador network.Ambassador + configOnce sync.Once + _logger *logrus.Entry + didDocCreator types.DocCreator +} + +var instance *VDR +var oneVDR sync.Once + +// Instance returns the singleton VDR +func Instance() *VDR { + if instance != nil { + return instance + } + oneVDR.Do(func() { + instance = NewVDR(DefaultConfig(), crypto.Instance(), networkClient.NewNetworkClient()) + }) + + return instance +} + +// NewVDR creates a new VDR with provided params +func NewVDR(config Config, cryptoClient crypto.KeyStore, networkClient pkg.NetworkClient) *VDR { + return &VDR{ + Config: config, + network: networkClient, + _logger: logging.Log(), + store: store.NewMemoryStore(), + didDocCreator: NutsDocCreator{keyCreator: cryptoClient}, + } +} + +// Configure initializes the db, but only when in server mode +func (r *VDR) Configure() error { + var err error + + r.configOnce.Do(func() { + cfg := core.NutsConfig() + if cfg.Mode() == core.ServerEngineMode { + if r.networkAmbassador == nil { + r.networkAmbassador = network.NewAmbassador(r.network) + } + } + }) + return err +} + +// Start initiates the routines for auto-updating the data +func (r *VDR) Start() error { + return nil +} + +// Shutdown cleans up any leftover go routines +func (r *VDR) Shutdown() error { + return nil +} + +// Diagnostics returns the diagnostics for this engine +func (r *VDR) Diagnostics() []core.DiagnosticResult { + return []core.DiagnosticResult{} +} + +func (r *VDR) getEventsDir() string { + return r.Config.Datadir + "/events" +} + +// Create generates a new DID Document +func (r VDR) Create() (*did.Document, error) { + doc, err := r.didDocCreator.Create() + if err != nil { + return nil, fmt.Errorf("could not create did document: %w", err) + } + + // Fixme: The doc should not be stored but send to the network. + metaData := types.DocumentMetadata{ + Created: time.Now(), + Version: 0, + } + err = r.store.Write(*doc, metaData) + if err != nil { + return nil, fmt.Errorf("could not store created did document: %w", err) + } + return doc, nil +} + +// Resolve resolves a DID Document based on the DID. +func (r VDR) Resolve(id did.DID, metadata *types.ResolveMetadata) (*did.Document, *types.DocumentMetadata, error) { + return r.store.Resolve(id, metadata) +} + +// Update updates a DID Document based on the DID and current hash +func (r VDR) Update(id did.DID, current hash.SHA256Hash, next did.Document, metadata *types.DocumentMetadata) error { + return r.store.Update(id, current, next, metadata) +} + +// Deactivate updates the DID Document so it can no longer be updated +func (r *VDR) Deactivate(DID did.DID, current hash.SHA256Hash) { + panic("implement me") +}