From 957bc33a7a51f33baabe5b087a4e6ce2a3c35daa Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Tue, 19 Jan 2021 08:46:04 +0100 Subject: [PATCH 01/54] added did.store interfaces --- did/api/v1/api.go | 27 ++ did/api/v1/client.go | 117 +++++ did/api/v1/generated.go | 935 +++++++++++++++++++++++++++++++++++++ did/engine/engine.go | 144 ++++++ did/engine/engine_test.go | 275 +++++++++++ did/logging/log.go | 31 ++ did/logging/log_test.go | 15 + did/network/ambassador.go | 84 ++++ did/network/mock.go | 45 ++ did/store.go | 219 +++++++++ docs/_static/did/v1.yaml | 205 ++++++++ docs/pages/api.rst | 3 +- docs/pages/development.rst | 3 +- go.mod | 2 + go.sum | 304 ++++++++++++ 15 files changed, 2407 insertions(+), 2 deletions(-) create mode 100644 did/api/v1/api.go create mode 100644 did/api/v1/client.go create mode 100644 did/api/v1/generated.go create mode 100644 did/engine/engine.go create mode 100644 did/engine/engine_test.go create mode 100644 did/logging/log.go create mode 100644 did/logging/log_test.go create mode 100644 did/network/ambassador.go create mode 100644 did/network/mock.go create mode 100644 did/store.go create mode 100644 docs/_static/did/v1.yaml diff --git a/did/api/v1/api.go b/did/api/v1/api.go new file mode 100644 index 0000000000..8ff41e7213 --- /dev/null +++ b/did/api/v1/api.go @@ -0,0 +1,27 @@ +/* + * Nuts registry + * Copyright (C) 2020. 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/nuts-node/did" + +// ApiWrapper is needed to connect the implementation to the echo ServiceWrapper +type ApiWrapper struct { + R did.Store +} diff --git a/did/api/v1/client.go b/did/api/v1/client.go new file mode 100644 index 0000000000..dc4170d408 --- /dev/null +++ b/did/api/v1/client.go @@ -0,0 +1,117 @@ +/* + * Nuts registry + * Copyright (C) 2020. 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" + "strings" + "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 + if !strings.Contains(url, "http") { + url = fmt.Sprintf("http://%v", hb.ServerAddress) + } + + response, err := NewClientWithResponses(url) + if err != nil { + panic(err) + } + return response +} + +func (hb HttpClient) Search(onlyOwn bool, tags []string) ([]did.Document, error) { + panic("implement me") +} + +func (hb HttpClient) Create() (*did.Document, error) { + if response, err := hb.client().CreateDID(context.Background()); err != nil { + return nil, err + } else if err := testResponseCode(http.StatusCreated, response); err != nil { + return nil, err + } else { + return readDIDDocument(response.Body) + } +} + +func (hb HttpClient) Get(DID did.DID) (*did.Document, *pkg.DIDDocumentMetadata, error) { + return hb.get(DID.String()) +} + +func (hb HttpClient) GetByTag(tag string) (*did.Document, *pkg.DIDDocumentMetadata, error) { + return hb.get("tag:" + tag) +} + +func (hb HttpClient) get(identifier string) (*did.Document, *pkg.DIDDocumentMetadata, error) { + response, err := hb.client().GetDID(context.Background(), identifier) + if err != nil { + return nil, nil, err + } + if err := testResponseCode(http.StatusOK, response); err != nil { + return nil, nil, err + } else { + // TODO: Parse response + return nil, nil, nil + } +} + +func (hb HttpClient) Update(DID did.DID, hash []byte, nextVersion did.Document) (*did.Document, error) { + panic("implement me") +} + +func (hb HttpClient) Tag(DID did.DID, tags []string) error { + panic("implement me") +} + +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) { + if data, err := ioutil.ReadAll(reader); err != nil { + return nil, fmt.Errorf("unable to read DID Document response: %w", err) + } else { + document := did.Document{} + if err := json.Unmarshal(data, &document); err != nil { + return nil, fmt.Errorf("unable to unmarshal DID Document response: %w", err) + } + return &document, nil + } +} diff --git a/did/api/v1/generated.go b/did/api/v1/generated.go new file mode 100644 index 0000000000..13cfc08602 --- /dev/null +++ b/did/api/v1/generated.go @@ -0,0 +1,935 @@ +// 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" + "time" + + "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/labstack/echo/v4" +) + +// DIDDocument defines model for DIDDocument. +type DIDDocument map[string]interface{} + +// DIDDocumentMetadata defines model for DIDDocumentMetadata. +type DIDDocumentMetadata struct { + + // Date/time at which the document was originally created. + Created *time.Time `json:"created,omitempty"` + + // Hash (SHA-256, hex-encoded) of DID document bytes. Is equal to payloadHash in network layer. + Hash *string `json:"hash,omitempty"` + + // Hash (SHA-256, hex-encoded) of the JWS envelope of the first version of the DID document. + OriginJwsHash *string `json:"originJwsHash,omitempty"` + + // Date/time at which the document (or this version) was updated. + Updated *time.Time `json:"updated,omitempty"` + + // Semantic version of the DID document. + Version *int `json:"version,omitempty"` +} + +// DIDResolutionResult defines model for DIDResolutionResult. +type DIDResolutionResult struct { + + // The actual DID Document in JSON representation. + Document *DIDDocument `json:"document,omitempty"` + DocumentMetadata *DIDDocumentMetadata `json:"documentMetadata,omitempty"` + + // Metadata collected during DID Document (a.k.a. DID Resolution Metadata). + ResolutionMetadata *map[string]interface{} `json:"resolutionMetadata,omitempty"` +} + +// SearchDIDParams defines parameters for SearchDID. +type SearchDIDParams struct { + + // URL encoded DID or tag. When given a tag it must resolve to exactly one DID. + Tags string `json:"tags"` +} + +// UpdateDIDJSONBody defines parameters for UpdateDID. +type UpdateDIDJSONBody struct { + + // SHA-256 hash of the last version of the DID Document + CurrentHash *string `json:"currentHash,omitempty"` + + // The actual DID Document in JSON representation. + Document *DIDDocument `json:"document,omitempty"` +} + +// UpdateDIDTagsJSONBody defines parameters for UpdateDIDTags. +type UpdateDIDTagsJSONBody []string + +// UpdateDIDRequestBody defines body for UpdateDID for application/json ContentType. +type UpdateDIDJSONRequestBody UpdateDIDJSONBody + +// UpdateDIDTagsRequestBody defines body for UpdateDIDTags for application/json ContentType. +type UpdateDIDTagsJSONRequestBody UpdateDIDTagsJSONBody + +// 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 { + // SearchDID request + SearchDID(ctx context.Context, params *SearchDIDParams) (*http.Response, error) + + // CreateDID request + CreateDID(ctx context.Context) (*http.Response, error) + + // GetDID request + GetDID(ctx context.Context, didOrTag string) (*http.Response, error) + + // UpdateDID request with any body + UpdateDIDWithBody(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*http.Response, error) + + UpdateDID(ctx context.Context, didOrTag string, body UpdateDIDJSONRequestBody) (*http.Response, error) + + // UpdateDIDTags request with any body + UpdateDIDTagsWithBody(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*http.Response, error) + + UpdateDIDTags(ctx context.Context, didOrTag string, body UpdateDIDTagsJSONRequestBody) (*http.Response, error) +} + +func (c *Client) SearchDID(ctx context.Context, params *SearchDIDParams) (*http.Response, error) { + req, err := NewSearchDIDRequest(c.Server, params) + 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) 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, didOrTag string) (*http.Response, error) { + req, err := NewGetDIDRequest(c.Server, didOrTag) + 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, didOrTag string, contentType string, body io.Reader) (*http.Response, error) { + req, err := NewUpdateDIDRequestWithBody(c.Server, didOrTag, 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, didOrTag string, body UpdateDIDJSONRequestBody) (*http.Response, error) { + req, err := NewUpdateDIDRequest(c.Server, didOrTag, 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) UpdateDIDTagsWithBody(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*http.Response, error) { + req, err := NewUpdateDIDTagsRequestWithBody(c.Server, didOrTag, 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) UpdateDIDTags(ctx context.Context, didOrTag string, body UpdateDIDTagsJSONRequestBody) (*http.Response, error) { + req, err := NewUpdateDIDTagsRequest(c.Server, didOrTag, 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) +} + +// NewSearchDIDRequest generates requests for SearchDID +func NewSearchDIDRequest(server string, params *SearchDIDParams) (*http.Request, error) { + var err error + + queryUrl, err := url.Parse(server) + if err != nil { + return nil, err + } + + basePath := fmt.Sprintf("/internal/registry/v1/did") + if basePath[0] == '/' { + basePath = basePath[1:] + } + + queryUrl, err = queryUrl.Parse(basePath) + if err != nil { + return nil, err + } + + queryValues := queryUrl.Query() + + if queryFrag, err := runtime.StyleParam("form", true, "tags", params.Tags); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryUrl.RawQuery = queryValues.Encode() + + req, err := http.NewRequest("GET", queryUrl.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// 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/registry/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, didOrTag string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParam("simple", false, "didOrTag", didOrTag) + if err != nil { + return nil, err + } + + queryUrl, err := url.Parse(server) + if err != nil { + return nil, err + } + + basePath := fmt.Sprintf("/internal/registry/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, didOrTag 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, didOrTag, "application/json", bodyReader) +} + +// NewUpdateDIDRequestWithBody generates requests for UpdateDID with any type of body +func NewUpdateDIDRequestWithBody(server string, didOrTag string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParam("simple", false, "didOrTag", didOrTag) + if err != nil { + return nil, err + } + + queryUrl, err := url.Parse(server) + if err != nil { + return nil, err + } + + basePath := fmt.Sprintf("/internal/registry/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 +} + +// NewUpdateDIDTagsRequest calls the generic UpdateDIDTags builder with application/json body +func NewUpdateDIDTagsRequest(server string, didOrTag string, body UpdateDIDTagsJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateDIDTagsRequestWithBody(server, didOrTag, "application/json", bodyReader) +} + +// NewUpdateDIDTagsRequestWithBody generates requests for UpdateDIDTags with any type of body +func NewUpdateDIDTagsRequestWithBody(server string, didOrTag string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParam("simple", false, "didOrTag", didOrTag) + if err != nil { + return nil, err + } + + queryUrl, err := url.Parse(server) + if err != nil { + return nil, err + } + + basePath := fmt.Sprintf("/internal/registry/v1/did/%s/tag", pathParam0) + if basePath[0] == '/' { + basePath = basePath[1:] + } + + queryUrl, err = queryUrl.Parse(basePath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", 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 { + // SearchDID request + SearchDIDWithResponse(ctx context.Context, params *SearchDIDParams) (*SearchDIDResponse, error) + + // CreateDID request + CreateDIDWithResponse(ctx context.Context) (*CreateDIDResponse, error) + + // GetDID request + GetDIDWithResponse(ctx context.Context, didOrTag string) (*GetDIDResponse, error) + + // UpdateDID request with any body + UpdateDIDWithBodyWithResponse(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*UpdateDIDResponse, error) + + UpdateDIDWithResponse(ctx context.Context, didOrTag string, body UpdateDIDJSONRequestBody) (*UpdateDIDResponse, error) + + // UpdateDIDTags request with any body + UpdateDIDTagsWithBodyWithResponse(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*UpdateDIDTagsResponse, error) + + UpdateDIDTagsWithResponse(ctx context.Context, didOrTag string, body UpdateDIDTagsJSONRequestBody) (*UpdateDIDTagsResponse, error) +} + +type SearchDIDResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]string +} + +// Status returns HTTPResponse.Status +func (r SearchDIDResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r SearchDIDResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +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 + JSON200 *DIDResolutionResult +} + +// 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 +} + +type UpdateDIDTagsResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r UpdateDIDTagsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateDIDTagsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// SearchDIDWithResponse request returning *SearchDIDResponse +func (c *ClientWithResponses) SearchDIDWithResponse(ctx context.Context, params *SearchDIDParams) (*SearchDIDResponse, error) { + rsp, err := c.SearchDID(ctx, params) + if err != nil { + return nil, err + } + return ParseSearchDIDResponse(rsp) +} + +// 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, didOrTag string) (*GetDIDResponse, error) { + rsp, err := c.GetDID(ctx, didOrTag) + if err != nil { + return nil, err + } + return ParseGetDIDResponse(rsp) +} + +// UpdateDIDWithBodyWithResponse request with arbitrary body returning *UpdateDIDResponse +func (c *ClientWithResponses) UpdateDIDWithBodyWithResponse(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*UpdateDIDResponse, error) { + rsp, err := c.UpdateDIDWithBody(ctx, didOrTag, contentType, body) + if err != nil { + return nil, err + } + return ParseUpdateDIDResponse(rsp) +} + +func (c *ClientWithResponses) UpdateDIDWithResponse(ctx context.Context, didOrTag string, body UpdateDIDJSONRequestBody) (*UpdateDIDResponse, error) { + rsp, err := c.UpdateDID(ctx, didOrTag, body) + if err != nil { + return nil, err + } + return ParseUpdateDIDResponse(rsp) +} + +// UpdateDIDTagsWithBodyWithResponse request with arbitrary body returning *UpdateDIDTagsResponse +func (c *ClientWithResponses) UpdateDIDTagsWithBodyWithResponse(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*UpdateDIDTagsResponse, error) { + rsp, err := c.UpdateDIDTagsWithBody(ctx, didOrTag, contentType, body) + if err != nil { + return nil, err + } + return ParseUpdateDIDTagsResponse(rsp) +} + +func (c *ClientWithResponses) UpdateDIDTagsWithResponse(ctx context.Context, didOrTag string, body UpdateDIDTagsJSONRequestBody) (*UpdateDIDTagsResponse, error) { + rsp, err := c.UpdateDIDTags(ctx, didOrTag, body) + if err != nil { + return nil, err + } + return ParseUpdateDIDTagsResponse(rsp) +} + +// ParseSearchDIDResponse parses an HTTP response from a SearchDIDWithResponse call +func ParseSearchDIDResponse(rsp *http.Response) (*SearchDIDResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &SearchDIDResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []string + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// 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 { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest DIDResolutionResult + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + 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 +} + +// ParseUpdateDIDTagsResponse parses an HTTP response from a UpdateDIDTagsWithResponse call +func ParseUpdateDIDTagsResponse(rsp *http.Response) (*UpdateDIDTagsResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer rsp.Body.Close() + if err != nil { + return nil, err + } + + response := &UpdateDIDTagsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Searches for Nuts DIDs + // (GET /internal/registry/v1/did) + SearchDID(ctx echo.Context, params SearchDIDParams) error + // Creates a new Nuts DID + // (POST /internal/registry/v1/did) + CreateDID(ctx echo.Context) error + // Resolves a Nuts DID Document + // (GET /internal/registry/v1/did/{didOrTag}) + GetDID(ctx echo.Context, didOrTag string) error + // Updates a Nuts DID Document + // (PUT /internal/registry/v1/did/{didOrTag}) + UpdateDID(ctx echo.Context, didOrTag string) error + // Replaces the tags of the DID Document. + // (POST /internal/registry/v1/did/{didOrTag}/tag) + UpdateDIDTags(ctx echo.Context, didOrTag string) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// SearchDID converts echo context to params. +func (w *ServerInterfaceWrapper) SearchDID(ctx echo.Context) error { + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params SearchDIDParams + // ------------- Required query parameter "tags" ------------- + + err = runtime.BindQueryParameter("form", true, true, "tags", ctx.QueryParams(), ¶ms.Tags) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter tags: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.SearchDID(ctx, params) + return err +} + +// 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 "didOrTag" ------------- + var didOrTag string + + err = runtime.BindStyledParameter("simple", false, "didOrTag", ctx.Param("didOrTag"), &didOrTag) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter didOrTag: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetDID(ctx, didOrTag) + return err +} + +// UpdateDID converts echo context to params. +func (w *ServerInterfaceWrapper) UpdateDID(ctx echo.Context) error { + var err error + // ------------- Path parameter "didOrTag" ------------- + var didOrTag string + + err = runtime.BindStyledParameter("simple", false, "didOrTag", ctx.Param("didOrTag"), &didOrTag) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter didOrTag: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.UpdateDID(ctx, didOrTag) + return err +} + +// UpdateDIDTags converts echo context to params. +func (w *ServerInterfaceWrapper) UpdateDIDTags(ctx echo.Context) error { + var err error + // ------------- Path parameter "didOrTag" ------------- + var didOrTag string + + err = runtime.BindStyledParameter("simple", false, "didOrTag", ctx.Param("didOrTag"), &didOrTag) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter didOrTag: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.UpdateDIDTags(ctx, didOrTag) + 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.GET(baseURL+"/internal/registry/v1/did", wrapper.SearchDID) + router.POST(baseURL+"/internal/registry/v1/did", wrapper.CreateDID) + router.GET(baseURL+"/internal/registry/v1/did/:didOrTag", wrapper.GetDID) + router.PUT(baseURL+"/internal/registry/v1/did/:didOrTag", wrapper.UpdateDID) + router.POST(baseURL+"/internal/registry/v1/did/:didOrTag/tag", wrapper.UpdateDIDTags) + +} + diff --git a/did/engine/engine.go b/did/engine/engine.go new file mode 100644 index 0000000000..63413a38e8 --- /dev/null +++ b/did/engine/engine.go @@ -0,0 +1,144 @@ +/* + * Nuts registry + * Copyright (C) 2020. 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 ( + "fmt" + + "github.com/nuts-foundation/nuts-network/pkg" + "github.com/nuts-foundation/nuts-node/did/logging" + + "github.com/nuts-foundation/nuts-node/core" + did "github.com/nuts-foundation/nuts-node/did" + api "github.com/nuts-foundation/nuts-node/did/api/v1" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// registryClientCreator is a variable to aid testability +var registryClientCreator = did.RegistryInstance() + +// NewRegistryEngine returns the core definition for the registry +func NewRegistryEngine() *core.Engine { + r := did.RegistryInstance() + + return &core.Engine{ + Cmd: cmd(), + Configure: r.Configure, + Config: &r.Config, + ConfigKey: "registry", + FlagSet: flagSet(), + Name: pkg.ModuleName, + Routes: func(router core.EchoRouter) { + api.RegisterHandlers(router, &api.ApiWrapper{R: r}) + }, + Start: r.Start, + Shutdown: r.Shutdown, + Diagnostics: r.Diagnostics, + } +} + +func flagSet() *pflag.FlagSet { + flagSet := pflag.NewFlagSet("registry", pflag.ContinueOnError) + + defs := did.DefaultRegistryConfig() + flagSet.String(did.ConfDataDir, defs.Datadir, fmt.Sprintf("Location of data files, default: %s", defs.Datadir)) + flagSet.String(did.ConfMode, defs.Mode, fmt.Sprintf("server or client, when client it uses the HttpClient, default: %s", defs.Mode)) + flagSet.String(did.ConfAddress, defs.Address, fmt.Sprintf("Interface and port for http server to bind to, default: %s", defs.Address)) + flagSet.Int(did.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: "registry", + Short: "registry commands", + } + + cmd.AddCommand(&cobra.Command{ + Use: "version", + Short: "Print the version number of the Nuts registry", + Run: func(cmd *cobra.Command, args []string) { + logging.Log().Errorf("version 0.0.0") + }, + }) + + cmd.AddCommand(&cobra.Command{ + Use: "search [tags]", + Short: "Find DIDs within the registry that have the given tags (comma-separated)", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + // split tags on comma and call Search + + //logging.Log().Errorf("Found %d organizations\n", len(os)) + }, + }) + + cmd.AddCommand(&cobra.Command{ + Use: "create-did", + Short: "Registers a new DID", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + + // call Create + + //logging.Log().Info("DID registered.") + return nil + }, + }) + + cmd.AddCommand(&cobra.Command{ + Use: "get [DID or tag]", + Short: "Find a DID document based on its DID or tag", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + // call Get or GetByTag + return nil + }, + }) + + cmd.AddCommand(&cobra.Command{ + Use: "tag DID [tags]", + Short: "Replace the tags of the given DID document with the given tags (comma-separated)", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + + // call Tag + return nil + }, + }) + + cmd.AddCommand(&cobra.Command{ + Use: "update DID [file]", + Short: "Update a DID with the given DID document, this replaces the DID document. If no file is given, stdin is used", + Args: cobra.RangeArgs(1, 2), + RunE: func(cmd *cobra.Command, args []string) error { + + // read from file or stdin + + // call Update + return nil + }, + }) + + return cmd +} diff --git a/did/engine/engine_test.go b/did/engine/engine_test.go new file mode 100644 index 0000000000..0bb4d1697f --- /dev/null +++ b/did/engine/engine_test.go @@ -0,0 +1,275 @@ +/* + * Nuts registry + * Copyright (C) 2020. 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 ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "errors" + "github.com/nuts-foundation/nuts-go-test/io" + "github.com/spf13/cobra" + "net" + "os" + "syscall" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/nuts-foundation/nuts-crypto/pkg/cert" + core "github.com/nuts-foundation/nuts-go-core" + "github.com/nuts-foundation/nuts-registry/mock" + "github.com/nuts-foundation/nuts-registry/pkg" + "github.com/nuts-foundation/nuts-registry/pkg/db" + "github.com/nuts-foundation/nuts-registry/pkg/events" + "github.com/nuts-foundation/nuts-registry/pkg/events/domain" + "github.com/nuts-foundation/nuts-registry/test" + "github.com/stretchr/testify/assert" +) + +func TestServer(t *testing.T) { + configureIdentity() + // Register test instance singleton + pkg.NewTestRegistryInstance(io.TestDirectory(t)) + command := cmd() + t.Run("SIGINT stops server", func(t *testing.T) { + command.SetArgs([]string{"server"}) + go func() { + println("Waiting for server to start...") + for { + conn, _ := net.Dial("tcp", pkg.DefaultRegistryConfig().Address) + if conn != nil { + println("Started!") + conn.Close() + break + } + time.Sleep(time.Second) + } + syscall.Kill(syscall.Getpid(), syscall.SIGINT) + }() + err := command.Execute() + assert.NoError(t, err) + }) +} + +func TestRegisterVendor(t *testing.T) { + // Register test instance singleton + pkg.NewTestRegistryInstance(io.TestDirectory(t)) + command := cmd() + t.Run("ok", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + client.EXPECT().RegisterVendor(gomock.Any()).Return(events.CreateEvent(domain.RegisterVendor, domain.RegisterVendorEvent{}, nil), nil) + command.SetArgs([]string{"register-vendor", "../test/certificate.pem"}) + err := command.Execute() + assert.NoError(t, err) + })) + t.Run("error - file does not exist", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + command.SetArgs([]string{"register-vendor", "non-existent"}) + err := command.Execute() + assert.EqualError(t, err, "open non-existent: no such file or directory") + })) + t.Run("error - invalid PEM", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + command.SetArgs([]string{"register-vendor", "../test/invalid.pem"}) + err := command.Execute() + assert.EqualError(t, err, "found 28 rest bytes after decoding PEM: failed to decode PEM block containing certificate") + })) + t.Run("error - unable to register", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + client.EXPECT().RegisterVendor(gomock.Any()).Return(nil, errors.New("failed")) + command.SetArgs([]string{"register-vendor", "../test/certificate.pem"}) + err := command.Execute() + assert.EqualError(t, err, "failed") + })) +} + +func TestVendorClaim(t *testing.T) { + // Register test instance singleton + pkg.NewTestRegistryInstance(io.TestDirectory(t)) + command := cmd() + orgID := test.OrganizationID("orgId") + t.Run("ok", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + event := events.CreateEvent(domain.VendorClaim, domain.RegisterVendorEvent{}, nil) + client.EXPECT().VendorClaim(orgID, "orgName", nil).Return(event, nil) + command.SetArgs([]string{"vendor-claim", orgID.String(), "orgName"}) + err := command.Execute() + assert.NoError(t, err) + })) + t.Run("error", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + client.EXPECT().VendorClaim(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("failed")) + command.SetArgs([]string{"vendor-claim", orgID.String(), "orgName"}) + command.Execute() + })) +} + +func TestRefreshOrganizationCertificate(t *testing.T) { + // Register test instance singleton + pkg.NewTestRegistryInstance(io.TestDirectory(t)) + command := cmd() + orgID := test.OrganizationID("123") + t.Run("ok", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + event := events.CreateEvent(domain.VendorClaim, domain.VendorClaimEvent{OrgKeys: []interface{}{generateCertificate()}}, nil) + client.EXPECT().RefreshOrganizationCertificate(orgID).Return(event, nil) + command.SetArgs([]string{"refresh-organization-cert", orgID.String()}) + err := command.Execute() + assert.NoError(t, err) + })) + t.Run("ok - no certs", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + event := events.CreateEvent(domain.VendorClaim, domain.VendorClaimEvent{}, nil) + client.EXPECT().RefreshOrganizationCertificate(orgID).Return(event, nil) + command.SetArgs([]string{"refresh-organization-cert", orgID.String()}) + err := command.Execute() + assert.NoError(t, err) + })) + t.Run("error", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + client.EXPECT().RefreshOrganizationCertificate(orgID).Return(nil, errors.New("failed")) + command.SetArgs([]string{"refresh-organization-cert", orgID.String()}) + command.Execute() + })) +} + +func TestVerify(t *testing.T) { + // Register test instance singleton + pkg.NewTestRegistryInstance(io.TestDirectory(t)) + t.Run("ok - fix data", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + client.EXPECT().Verify(true).Return(nil, false, nil) + command := cmd() + command.SetArgs([]string{"verify", "-f"}) + err := command.Execute() + assert.NoError(t, err) + })) + t.Run("ok - nothing to do", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + client.EXPECT().Verify(false).Return(nil, false, nil) + command := cmd() + command.SetArgs([]string{"verify"}) + err := command.Execute() + assert.NoError(t, err) + })) + + t.Run("ok - data needs fixing", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + client.EXPECT().Verify(false).Return(nil, true, nil) + command := cmd() + command.SetArgs([]string{"verify"}) + err := command.Execute() + assert.NoError(t, err) + })) + t.Run("ok - events emitted", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + client.EXPECT().Verify(true).Return([]events.Event{events.CreateEvent("foobar", struct{}{}, nil)}, true, nil) + command := cmd() + command.SetArgs([]string{"verify", "-f"}) + err := command.Execute() + assert.NoError(t, err) + })) + t.Run("error", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + client.EXPECT().Verify(false).Return(nil, false, errors.New("failed")) + command := cmd() + command.SetArgs([]string{"verify"}) + err := command.Execute() + assert.Error(t, err) + })) +} + +func TestRegisterEndpoint(t *testing.T) { + // Register test instance singleton + pkg.NewTestRegistryInstance(io.TestDirectory(t)) + command := cmd() + var orgID, _ = core.ParsePartyID("urn:oid:1.2.3:foo") + t.Run("ok - bare minimum parameters", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + event := events.CreateEvent(domain.RegisterEndpoint, domain.RegisterEndpointEvent{}, nil) + client.EXPECT().RegisterEndpoint(orgID, "", "url", "type", db.StatusActive, map[string]string{}).Return(event, nil) + command.SetArgs([]string{"register-endpoint", orgID.String(), "type", "url"}) + err := command.Execute() + assert.NoError(t, err) + })) + t.Run("ok - all parameters", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + event := events.CreateEvent(domain.RegisterEndpoint, domain.RegisterEndpointEvent{}, nil) + client.EXPECT().RegisterEndpoint(orgID, "id", "url", "type", db.StatusActive, map[string]string{"k1": "v1", "k2": "v2"}).Return(event, nil) + command.SetArgs([]string{"register-endpoint", orgID.String(), "type", "url", "-i", "id", "-p", "k1=v1", "-p", "k2=v2"}) + err := command.Execute() + assert.NoError(t, err) + })) + t.Run("error", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + client.EXPECT().RegisterEndpoint(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("failed")) + command.SetArgs([]string{"register-endpoint", orgID.String(), "type", "url"}) + command.Execute() + })) +} + +func TestSearchOrg(t *testing.T) { + // Register test instance singleton + pkg.NewTestRegistryInstance(io.TestDirectory(t)) + command := cmd() + t.Run("ok", withMock(func(t *testing.T, client *mock.MockRegistryClient) { + client.EXPECT().SearchOrganizations("foo") + command.SetArgs([]string{"search", "foo"}) + err := command.Execute() + assert.NoError(t, err) + })) +} + +func TestPrintVersion(t *testing.T) { + // Register test instance singleton + pkg.NewTestRegistryInstance(io.TestDirectory(t)) + command := cmd() + command.SetArgs([]string{"version"}) + err := command.Execute() + assert.NoError(t, err) +} + +func Test_flagSet(t *testing.T) { + assert.NotNil(t, flagSet()) +} + +func TestNewRegistryEngine(t *testing.T) { + // Register test instance singleton + pkg.NewTestRegistryInstance(io.TestDirectory(t)) + t.Run("instance", func(t *testing.T) { + assert.NotNil(t, NewRegistryEngine()) + }) + + t.Run("configuration", func(t *testing.T) { + e := NewRegistryEngine() + cfg := core.NutsConfig() + cfg.RegisterFlags(e.Cmd, e) + assert.NoError(t, cfg.InjectIntoEngine(e)) + }) +} + +func withMock(test func(t *testing.T, client *mock.MockRegistryClient)) func(t *testing.T) { + return func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + registryClient := mock.NewMockRegistryClient(mockCtrl) + registryClientCreator = func() pkg.RegistryClient { + return registryClient + } + test(t, registryClient) + } +} + +func generateCertificate() map[string]interface{} { + privateKey, _ := rsa.GenerateKey(rand.Reader, 1024) + certAsBytes := test.GenerateCertificateEx(time.Now(), 1, privateKey) + certificate, _ := x509.ParseCertificate(certAsBytes) + certAsJWK, _ := cert.CertificateToJWK(certificate) + certAsMap, _ := cert.JwkToMap(certAsJWK) + return certAsMap +} + +func configureIdentity() { + os.Setenv("NUTS_IDENTITY", test.VendorID("4").String()) + core.NutsConfig().Load(&cobra.Command{}) +} diff --git a/did/logging/log.go b/did/logging/log.go new file mode 100644 index 0000000000..f15168021f --- /dev/null +++ b/did/logging/log.go @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020. 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", "DID Store") + +// 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/did/logging/log_test.go b/did/logging/log_test.go new file mode 100644 index 0000000000..1b40f7041b --- /dev/null +++ b/did/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"], "DID Store") + }) +} diff --git a/did/network/ambassador.go b/did/network/ambassador.go new file mode 100644 index 0000000000..74cdb28de4 --- /dev/null +++ b/did/network/ambassador.go @@ -0,0 +1,84 @@ +/* + * Nuts registry + * Copyright (C) 2020. 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/crypto" + "github.com/nuts-foundation/nuts-node/did/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 + cryptoClient crypto.KeyStore +} + +// NewAmbassador creates a new Ambassador. Don't forget to call RegisterEventHandlers afterwards. +func NewAmbassador(networkClient network.NetworkClient, cryptoClient crypto.KeyStore) Ambassador { + instance := &ambassador{ + networkClient: networkClient, + cryptoClient: cryptoClient, + } + 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/did/network/mock.go b/did/network/mock.go new file mode 100644 index 0000000000..0a565ffd39 --- /dev/null +++ b/did/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/did/store.go b/did/store.go new file mode 100644 index 0000000000..6cb77450ee --- /dev/null +++ b/did/store.go @@ -0,0 +1,219 @@ +/* + * Nuts registry + * Copyright (C) 2020. 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 pkg + +import ( + "sync" + "time" + + "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-network/pkg/model" + core2 "github.com/nuts-foundation/nuts-node/core" + "github.com/nuts-foundation/nuts-node/did/logging" + "github.com/sirupsen/logrus" + + "github.com/nuts-foundation/nuts-network/pkg" + "github.com/nuts-foundation/nuts-node/did/network" + + "github.com/nuts-foundation/nuts-crypto/client" + crypto "github.com/nuts-foundation/nuts-crypto/pkg" + core "github.com/nuts-foundation/nuts-go-core" + networkClient "github.com/nuts-foundation/nuts-network/client" + networkPkg "github.com/nuts-foundation/nuts-network/pkg" +) + +// ConfDataDir is the config name for specifiying the data location of the requiredFiles +const ConfDataDir = "datadir" + +// ConfMode is the config name for the engine mode, server or client +const ConfMode = "mode" + +// ConfAddress is the config name for the http server/client address +const ConfAddress = "address" + +// ConfSyncMode is the config name for the used SyncMode +const ConfSyncMode = "syncMode" + +// ConfSyncAddress is the config name for the remote address used to fetch updated registry files +const ConfSyncAddress = "syncAddress" + +// ConfSyncInterval is the config name for the interval in minutes to look for new registry files online +const ConfSyncInterval = "syncInterval" + +// ConfOrganisationCertificateValidity is the config name for the number of days organisation certificates are valid +const ConfOrganisationCertificateValidity = "organisationCertificateValidity" + +// ConfVendorCACertificateValidity is the config name for the number of days vendor CA certificates are valid +const ConfVendorCACertificateValidity = "vendorCACertificateValidity" + +// ConfClientTimeout is the time-out for the client in seconds (e.g. when using the CLI). +const ConfClientTimeout = "clientTimeout" + +// ModuleName == Registry +const ModuleName = "Registry" + +// ReloadRegistryIdleTimeout defines the cooling down period after receiving a file watcher notification, before +// the registry is reloaded (from disk). +var ReloadRegistryIdleTimeout time.Duration + +// Store is the interface for the low level DID operations. +type Store interface { + // Search searches for DID documents that match the given conditions; + // - onlyOwn: only return documents which contain a verificationMethod which' private key is present in this node. + // - tags: only return documents that match ALL of the given tags. + // If something goes wrong an error is returned. + Search(onlyOwn bool, tags []string) ([]did.Document, error) + // Create creates a new DID document and returns it. If something goes wrong an error is returned. + Create() (*did.Document, error) + // Get returns the DID document using on the given DID or nil if not found. If something goes wrong an error is returned. + Get(DID did.DID) (*did.Document, *DocumentMetadata, error) + // GetByTag gets a DID document using the given tag or nil if not found. If multiple documents match the given tag + // or something else goes wrong, an error is returned. + GetByTag(tag string) (*did.Document, *DocumentMetadata, error) + // Update replaces the DID document identified by DID with the nextVersion if the given hash matches the current valid DID document hash. + Update(DID did.DID, hash []byte, nextVersion did.Document) (*did.Document, error) + // Tag replaces all tags on a DID document given the DID. + Tag(DID did.DID, tags []string) error +} + +// 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 model.Hash `json:"originJwsHash"` + // Hash of DID document bytes. Is equal to payloadHash in network layer. + Hash string `json:"hash"` + // Tags of the DID document. + Tags []string `json:"tags,omitempty"` +} + +//type StoreWrapper struct { +// networkClient networkPkg.NetworkClient +// store DIDStore +//} +// +//func wrap(store DIDStore) DIDStore { +// return &StoreWrapper(store: store) +//} + +// Config holds the config +type Config struct { + Mode string + Datadir string + Address string + ClientTimeout int +} + +func DefaultRegistryConfig() Config { + return Config{ + Datadir: "./data", + Address: "localhost:1323", + ClientTimeout: 10, + } +} + +// Registry holds the config and Db reference +type Registry struct { + Config Config + //Db db.Db + network networkPkg.NetworkClient + crypto crypto.Client + OnChange func(registry *Registry) + networkAmbassador network.Ambassador + configOnce sync.Once + _logger *logrus.Entry + closers []chan struct{} +} + +var instance *Registry +var oneRegistry sync.Once + +func init() { + ReloadRegistryIdleTimeout = 3 * time.Second +} + +// RegistryInstance returns the singleton Registry +func RegistryInstance() *Registry { + if instance != nil { + return instance + } + oneRegistry.Do(func() { + instance = NewRegistryInstance(DefaultRegistryConfig(), client.NewCryptoClient(), networkClient.NewNetworkClient()) + }) + + return instance +} + +func NewRegistryInstance(config Config, cryptoClient crypto.Client, networkClient pkg.NetworkClient) *Registry { + return &Registry{ + Config: config, + crypto: cryptoClient, + network: networkClient, + _logger: logging.Log(), + } +} + +// Configure initializes the db, but only when in server mode +func (r *Registry) Configure() error { + var err error + + r.configOnce.Do(func() { + cfg := core.NutsConfig() + r.Config.Mode = cfg.GetEngineMode(r.Config.Mode) + if r.Config.Mode == core.ServerEngineMode { + //r.Db = db.New() + if r.networkAmbassador == nil { + r.networkAmbassador = network.NewAmbassador(r.network, r.crypto) + } + } + }) + return err +} + +// Start initiates the routines for auto-updating the data +func (r *Registry) Start() error { + if r.Config.Mode == core.ServerEngineMode { + r.networkAmbassador.Start() + } + return nil +} + +// Shutdown cleans up any leftover go routines +func (r *Registry) Shutdown() error { + if r.Config.Mode == core.ServerEngineMode { + logging.Log().Debug("Sending close signal to all routines") + for _, ch := range r.closers { + ch <- struct{}{} + } + logging.Log().Info("All routines closed") + } + return nil +} + +func (r *Registry) Diagnostics() []core2.DiagnosticResult { + return []core2.DiagnosticResult{} +} + +func (r *Registry) getEventsDir() string { + return r.Config.Datadir + "/events" +} diff --git a/docs/_static/did/v1.yaml b/docs/_static/did/v1.yaml new file mode 100644 index 0000000000..a2671fd61e --- /dev/null +++ b/docs/_static/did/v1.yaml @@ -0,0 +1,205 @@ +openapi: "3.0.0" +info: + title: Nuts registry API spec + description: API specification for RPC services available at the nuts-registry + version: 1.0.0 + license: + name: GPLv3 +paths: + /internal/registry/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 + get: + summary: "Searches for Nuts DIDs" + operationId: searchDID + tags: + - DID + parameters: + - name: tags + in: query + description: "URL encoded DID or tag. When given a tag it must resolve to exactly one DID." + required: true + example: + - "did:nuts:1234" + - "tag:client-987731" + schema: + type: string + responses: + "200": + description: "Search successful, response contains the DIDs" + content: + application/json: + schema: + type: array + items: + type: string + "500": + description: "An error occurred while processing the request." + content: + text/plain: + schema: + type: string + /internal/registry/v1/did/{didOrTag}: + parameters: + - name: didOrTag + in: path + description: "URL encoded DID or tag. When given a tag it must resolve to exactly one DID." + required: true + example: + - "did:nuts:1234" + - "tag:client-987731" + 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: + schema: + $ref: "#/components/schemas/DIDResolutionResult" + "404": + description: "DID couldn't 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: + properties: + document: + $ref: "#/components/schemas/DIDDocument" + currentHash: + description: "SHA-256 hash of the last version of the DID Document" + type: string + 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." + content: + text/plain: + schema: + type: string + "404": + description: "DID couldn't be found." + content: + text/plain: + schema: + type: string + "500": + description: "An error occurred while processing the request." + content: + text/plain: + schema: + type: string + /internal/registry/v1/did/{didOrTag}/tag: + parameters: + - name: didOrTag + in: path + description: "URL encoded DID or tag. When given a tag it must resolve to exactly one DID." + required: true + example: + - "did:nuts:1234" + - "tag:client-987731" + schema: + type: string + post: + summary: "Replaces the tags of the DID Document." + operationId: updateDIDTags + requestBody: + description: "The new set of tags for the DID Document." + required: true + content: + application/json: + schema: + type: array + items: + type: string + responses: + "204": + description: "DID Document tags have been updated." + "404": + description: "DID couldn't 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: The actual DID Document in JSON representation. + DIDDocumentMetadata: + properties: + created: + description: "Date/time at which the document was originally created." + type: string + format: date-time + updated: + description: "Date/time at which the document (or this version) was updated." + type: string + format: date-time + version: + description: "Semantic version of the DID document." + type: integer + originJwsHash: + description: "Hash (SHA-256, hex-encoded) of the JWS envelope of the first version of the DID document." + type: string + hash: + description: "Hash (SHA-256, hex-encoded) of DID document bytes. Is equal to payloadHash in network layer." + type: string + DIDResolutionResult: + properties: + document: + $ref: "#/components/schemas/DIDDocument" + documentMetadata: + $ref: '#/components/schemas/DIDDocumentMetadata' + resolutionMetadata: + type: object + description: Metadata collected during DID Document (a.k.a. DID Resolution Metadata). \ No newline at end of file diff --git a/docs/pages/api.rst b/docs/pages/api.rst index 1a01c16117..4e97ac9375 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: "example"}, + {url: "../_static/did/v1.yaml", name: "example"}, ], presets: [ SwaggerUIBundle.presets.apis, diff --git a/docs/pages/development.rst b/docs/pages/development.rst index 2bdbdc5fc8..079023aa65 100644 --- a/docs/pages/development.rst +++ b/docs/pages/development.rst @@ -28,7 +28,8 @@ 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 + oapi-codegen -generate types,server,client -package v1 docs/_static/crypto/v1.yaml > crypto/api/v1/generated.go + oapi-codegen -generate types,server,client -package v1 docs/_static/did/v1.yaml > did/api/v1/generated.go Generating Mocks **************** diff --git a/go.mod b/go.mod index 08d9de0b13..491982a03d 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,8 @@ require ( 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-20210114144203-c658fb4d0f65 // indirect + github.com/nuts-foundation/nuts-network v0.16.0 // indirect github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.7.1 github.com/sirupsen/logrus v1.7.0 diff --git a/go.sum b/go.sum index e890b9b161..a2573e3840 100644 --- a/go.sum +++ b/go.sum @@ -1,76 +1,144 @@ 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/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/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/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/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 +146,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 +154,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 +165,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 +198,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 +214,60 @@ 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/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 +281,45 @@ 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/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/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 +336,33 @@ 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-20210114144203-c658fb4d0f65 h1:0ZsmVFuJpJmcMGM0GGq1V2m4eN7q1ef28GzaCWJ24Oo= +github.com/nuts-foundation/go-did v0.0.0-20210114144203-c658fb4d0f65/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/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 +372,49 @@ 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/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 +426,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 +445,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 +454,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 +463,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 +507,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 +521,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 +550,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 +577,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 +624,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 +634,57 @@ 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/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 +692,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 +726,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= From 7390a197036f0b370c65e90b7f621b0bc2a2fdd5 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Tue, 19 Jan 2021 09:01:29 +0100 Subject: [PATCH 02/54] some migration errors --- did/api/v1/api.go | 26 +++++++++++++++++++++++++- did/api/v1/client.go | 6 +++--- did/engine/engine.go | 2 +- did/store.go | 42 ++++++++++++++++++++++++++++++++---------- go.mod | 1 - 5 files changed, 61 insertions(+), 16 deletions(-) diff --git a/did/api/v1/api.go b/did/api/v1/api.go index 8ff41e7213..7390847276 100644 --- a/did/api/v1/api.go +++ b/did/api/v1/api.go @@ -19,9 +19,33 @@ package v1 -import "github.com/nuts-foundation/nuts-node/did" +import ( + "github.com/labstack/echo/v4" + "github.com/nuts-foundation/nuts-node/did" +) // ApiWrapper is needed to connect the implementation to the echo ServiceWrapper type ApiWrapper struct { R did.Store } + +func (a ApiWrapper) SearchDID(ctx echo.Context, params SearchDIDParams) error { + panic("implement me") +} + +func (a ApiWrapper) CreateDID(ctx echo.Context) error { + panic("implement me") +} + +func (a ApiWrapper) GetDID(ctx echo.Context, didOrTag string) error { + panic("implement me") +} + +func (a ApiWrapper) UpdateDID(ctx echo.Context, didOrTag string) error { + panic("implement me") +} + +func (a ApiWrapper) UpdateDIDTags(ctx echo.Context, didOrTag string) error { + panic("implement me") +} + diff --git a/did/api/v1/client.go b/did/api/v1/client.go index dc4170d408..bad185a391 100644 --- a/did/api/v1/client.go +++ b/did/api/v1/client.go @@ -66,15 +66,15 @@ func (hb HttpClient) Create() (*did.Document, error) { } } -func (hb HttpClient) Get(DID did.DID) (*did.Document, *pkg.DIDDocumentMetadata, error) { +func (hb HttpClient) Get(DID did.DID) (*did.Document, *did.DocumentMetadata, error) { return hb.get(DID.String()) } -func (hb HttpClient) GetByTag(tag string) (*did.Document, *pkg.DIDDocumentMetadata, error) { +func (hb HttpClient) GetByTag(tag string) (*did.Document, *did.DocumentMetadata, error) { return hb.get("tag:" + tag) } -func (hb HttpClient) get(identifier string) (*did.Document, *pkg.DIDDocumentMetadata, error) { +func (hb HttpClient) get(identifier string) (*did.Document, *did.DocumentMetadata, error) { response, err := hb.client().GetDID(context.Background(), identifier) if err != nil { return nil, nil, err diff --git a/did/engine/engine.go b/did/engine/engine.go index 63413a38e8..7c9dc1b39f 100644 --- a/did/engine/engine.go +++ b/did/engine/engine.go @@ -26,7 +26,7 @@ import ( "github.com/nuts-foundation/nuts-node/did/logging" "github.com/nuts-foundation/nuts-node/core" - did "github.com/nuts-foundation/nuts-node/did" + "github.com/nuts-foundation/nuts-node/did" api "github.com/nuts-foundation/nuts-node/did/api/v1" "github.com/spf13/cobra" "github.com/spf13/pflag" diff --git a/did/store.go b/did/store.go index 6cb77450ee..ab782052a4 100644 --- a/did/store.go +++ b/did/store.go @@ -17,7 +17,7 @@ * */ -package pkg +package did import ( "sync" @@ -25,18 +25,16 @@ import ( "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/nuts-network/pkg/model" - core2 "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/did/logging" "github.com/sirupsen/logrus" "github.com/nuts-foundation/nuts-network/pkg" "github.com/nuts-foundation/nuts-node/did/network" - "github.com/nuts-foundation/nuts-crypto/client" - crypto "github.com/nuts-foundation/nuts-crypto/pkg" - core "github.com/nuts-foundation/nuts-go-core" + "github.com/nuts-foundation/nuts-node/core" networkClient "github.com/nuts-foundation/nuts-network/client" networkPkg "github.com/nuts-foundation/nuts-network/pkg" + "github.com/nuts-foundation/nuts-node/crypto" ) // ConfDataDir is the config name for specifiying the data location of the requiredFiles @@ -137,7 +135,7 @@ type Registry struct { Config Config //Db db.Db network networkPkg.NetworkClient - crypto crypto.Client + crypto crypto.KeyStore OnChange func(registry *Registry) networkAmbassador network.Ambassador configOnce sync.Once @@ -158,13 +156,13 @@ func RegistryInstance() *Registry { return instance } oneRegistry.Do(func() { - instance = NewRegistryInstance(DefaultRegistryConfig(), client.NewCryptoClient(), networkClient.NewNetworkClient()) + instance = NewRegistryInstance(DefaultRegistryConfig(), crypto.Instance(), networkClient.NewNetworkClient()) }) return instance } -func NewRegistryInstance(config Config, cryptoClient crypto.Client, networkClient pkg.NetworkClient) *Registry { +func NewRegistryInstance(config Config, cryptoClient crypto.KeyStore, networkClient pkg.NetworkClient) *Registry { return &Registry{ Config: config, crypto: cryptoClient, @@ -210,10 +208,34 @@ func (r *Registry) Shutdown() error { return nil } -func (r *Registry) Diagnostics() []core2.DiagnosticResult { - return []core2.DiagnosticResult{} +func (r *Registry) Diagnostics() []core.DiagnosticResult { + return []core.DiagnosticResult{} } func (r *Registry) getEventsDir() string { return r.Config.Datadir + "/events" } + +func (r *Registry) Search(onlyOwn bool, tags []string) ([]did.Document, error) { + panic("implement me") +} + +func (r *Registry) Create() (*did.Document, error) { + panic("implement me") +} + +func (r *Registry) Get(DID did.DID) (*did.Document, *DocumentMetadata, error) { + panic("implement me") +} + +func (r *Registry) GetByTag(tag string) (*did.Document, *DocumentMetadata, error) { + panic("implement me") +} + +func (r *Registry) Update(DID did.DID, hash []byte, nextVersion did.Document) (*did.Document, error) { + panic("implement me") +} + +func (r *Registry) Tag(DID did.DID, tags []string) error { + panic("implement me") +} diff --git a/go.mod b/go.mod index 491982a03d..3358a79f0d 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ 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 From 0bc6ed12078dfa6ee34266e4833bbe861bd9a8e1 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Tue, 19 Jan 2021 09:55:45 +0100 Subject: [PATCH 03/54] Update registry structure Renamed from did to registry Introduced a types.go with interfaces and types Moved all config related things to config.go Removed obsolete engine tests --- did/engine/engine_test.go | 275 ------------------------ go.mod | 8 +- go.sum | 18 ++ {did => registry}/api/v1/api.go | 4 +- {did => registry}/api/v1/client.go | 0 {did => registry}/api/v1/generated.go | 0 registry/config.go | 49 +++++ {did => registry}/engine/engine.go | 20 +- registry/engine/engine_test.go | 57 +++++ {did => registry}/logging/log.go | 0 {did => registry}/logging/log_test.go | 0 {did => registry}/network/ambassador.go | 2 +- {did => registry}/network/mock.go | 0 did/store.go => registry/registry.go | 102 ++------- registry/types.go | 42 ++++ 15 files changed, 199 insertions(+), 378 deletions(-) delete mode 100644 did/engine/engine_test.go rename {did => registry}/api/v1/api.go (95%) rename {did => registry}/api/v1/client.go (100%) rename {did => registry}/api/v1/generated.go (100%) create mode 100644 registry/config.go rename {did => registry}/engine/engine.go (79%) create mode 100644 registry/engine/engine_test.go rename {did => registry}/logging/log.go (100%) rename {did => registry}/logging/log_test.go (100%) rename {did => registry}/network/ambassador.go (97%) rename {did => registry}/network/mock.go (100%) rename did/store.go => registry/registry.go (53%) create mode 100644 registry/types.go diff --git a/did/engine/engine_test.go b/did/engine/engine_test.go deleted file mode 100644 index 0bb4d1697f..0000000000 --- a/did/engine/engine_test.go +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Nuts registry - * Copyright (C) 2020. 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 ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "errors" - "github.com/nuts-foundation/nuts-go-test/io" - "github.com/spf13/cobra" - "net" - "os" - "syscall" - "testing" - "time" - - "github.com/golang/mock/gomock" - "github.com/nuts-foundation/nuts-crypto/pkg/cert" - core "github.com/nuts-foundation/nuts-go-core" - "github.com/nuts-foundation/nuts-registry/mock" - "github.com/nuts-foundation/nuts-registry/pkg" - "github.com/nuts-foundation/nuts-registry/pkg/db" - "github.com/nuts-foundation/nuts-registry/pkg/events" - "github.com/nuts-foundation/nuts-registry/pkg/events/domain" - "github.com/nuts-foundation/nuts-registry/test" - "github.com/stretchr/testify/assert" -) - -func TestServer(t *testing.T) { - configureIdentity() - // Register test instance singleton - pkg.NewTestRegistryInstance(io.TestDirectory(t)) - command := cmd() - t.Run("SIGINT stops server", func(t *testing.T) { - command.SetArgs([]string{"server"}) - go func() { - println("Waiting for server to start...") - for { - conn, _ := net.Dial("tcp", pkg.DefaultRegistryConfig().Address) - if conn != nil { - println("Started!") - conn.Close() - break - } - time.Sleep(time.Second) - } - syscall.Kill(syscall.Getpid(), syscall.SIGINT) - }() - err := command.Execute() - assert.NoError(t, err) - }) -} - -func TestRegisterVendor(t *testing.T) { - // Register test instance singleton - pkg.NewTestRegistryInstance(io.TestDirectory(t)) - command := cmd() - t.Run("ok", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - client.EXPECT().RegisterVendor(gomock.Any()).Return(events.CreateEvent(domain.RegisterVendor, domain.RegisterVendorEvent{}, nil), nil) - command.SetArgs([]string{"register-vendor", "../test/certificate.pem"}) - err := command.Execute() - assert.NoError(t, err) - })) - t.Run("error - file does not exist", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - command.SetArgs([]string{"register-vendor", "non-existent"}) - err := command.Execute() - assert.EqualError(t, err, "open non-existent: no such file or directory") - })) - t.Run("error - invalid PEM", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - command.SetArgs([]string{"register-vendor", "../test/invalid.pem"}) - err := command.Execute() - assert.EqualError(t, err, "found 28 rest bytes after decoding PEM: failed to decode PEM block containing certificate") - })) - t.Run("error - unable to register", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - client.EXPECT().RegisterVendor(gomock.Any()).Return(nil, errors.New("failed")) - command.SetArgs([]string{"register-vendor", "../test/certificate.pem"}) - err := command.Execute() - assert.EqualError(t, err, "failed") - })) -} - -func TestVendorClaim(t *testing.T) { - // Register test instance singleton - pkg.NewTestRegistryInstance(io.TestDirectory(t)) - command := cmd() - orgID := test.OrganizationID("orgId") - t.Run("ok", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - event := events.CreateEvent(domain.VendorClaim, domain.RegisterVendorEvent{}, nil) - client.EXPECT().VendorClaim(orgID, "orgName", nil).Return(event, nil) - command.SetArgs([]string{"vendor-claim", orgID.String(), "orgName"}) - err := command.Execute() - assert.NoError(t, err) - })) - t.Run("error", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - client.EXPECT().VendorClaim(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("failed")) - command.SetArgs([]string{"vendor-claim", orgID.String(), "orgName"}) - command.Execute() - })) -} - -func TestRefreshOrganizationCertificate(t *testing.T) { - // Register test instance singleton - pkg.NewTestRegistryInstance(io.TestDirectory(t)) - command := cmd() - orgID := test.OrganizationID("123") - t.Run("ok", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - event := events.CreateEvent(domain.VendorClaim, domain.VendorClaimEvent{OrgKeys: []interface{}{generateCertificate()}}, nil) - client.EXPECT().RefreshOrganizationCertificate(orgID).Return(event, nil) - command.SetArgs([]string{"refresh-organization-cert", orgID.String()}) - err := command.Execute() - assert.NoError(t, err) - })) - t.Run("ok - no certs", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - event := events.CreateEvent(domain.VendorClaim, domain.VendorClaimEvent{}, nil) - client.EXPECT().RefreshOrganizationCertificate(orgID).Return(event, nil) - command.SetArgs([]string{"refresh-organization-cert", orgID.String()}) - err := command.Execute() - assert.NoError(t, err) - })) - t.Run("error", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - client.EXPECT().RefreshOrganizationCertificate(orgID).Return(nil, errors.New("failed")) - command.SetArgs([]string{"refresh-organization-cert", orgID.String()}) - command.Execute() - })) -} - -func TestVerify(t *testing.T) { - // Register test instance singleton - pkg.NewTestRegistryInstance(io.TestDirectory(t)) - t.Run("ok - fix data", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - client.EXPECT().Verify(true).Return(nil, false, nil) - command := cmd() - command.SetArgs([]string{"verify", "-f"}) - err := command.Execute() - assert.NoError(t, err) - })) - t.Run("ok - nothing to do", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - client.EXPECT().Verify(false).Return(nil, false, nil) - command := cmd() - command.SetArgs([]string{"verify"}) - err := command.Execute() - assert.NoError(t, err) - })) - - t.Run("ok - data needs fixing", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - client.EXPECT().Verify(false).Return(nil, true, nil) - command := cmd() - command.SetArgs([]string{"verify"}) - err := command.Execute() - assert.NoError(t, err) - })) - t.Run("ok - events emitted", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - client.EXPECT().Verify(true).Return([]events.Event{events.CreateEvent("foobar", struct{}{}, nil)}, true, nil) - command := cmd() - command.SetArgs([]string{"verify", "-f"}) - err := command.Execute() - assert.NoError(t, err) - })) - t.Run("error", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - client.EXPECT().Verify(false).Return(nil, false, errors.New("failed")) - command := cmd() - command.SetArgs([]string{"verify"}) - err := command.Execute() - assert.Error(t, err) - })) -} - -func TestRegisterEndpoint(t *testing.T) { - // Register test instance singleton - pkg.NewTestRegistryInstance(io.TestDirectory(t)) - command := cmd() - var orgID, _ = core.ParsePartyID("urn:oid:1.2.3:foo") - t.Run("ok - bare minimum parameters", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - event := events.CreateEvent(domain.RegisterEndpoint, domain.RegisterEndpointEvent{}, nil) - client.EXPECT().RegisterEndpoint(orgID, "", "url", "type", db.StatusActive, map[string]string{}).Return(event, nil) - command.SetArgs([]string{"register-endpoint", orgID.String(), "type", "url"}) - err := command.Execute() - assert.NoError(t, err) - })) - t.Run("ok - all parameters", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - event := events.CreateEvent(domain.RegisterEndpoint, domain.RegisterEndpointEvent{}, nil) - client.EXPECT().RegisterEndpoint(orgID, "id", "url", "type", db.StatusActive, map[string]string{"k1": "v1", "k2": "v2"}).Return(event, nil) - command.SetArgs([]string{"register-endpoint", orgID.String(), "type", "url", "-i", "id", "-p", "k1=v1", "-p", "k2=v2"}) - err := command.Execute() - assert.NoError(t, err) - })) - t.Run("error", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - client.EXPECT().RegisterEndpoint(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("failed")) - command.SetArgs([]string{"register-endpoint", orgID.String(), "type", "url"}) - command.Execute() - })) -} - -func TestSearchOrg(t *testing.T) { - // Register test instance singleton - pkg.NewTestRegistryInstance(io.TestDirectory(t)) - command := cmd() - t.Run("ok", withMock(func(t *testing.T, client *mock.MockRegistryClient) { - client.EXPECT().SearchOrganizations("foo") - command.SetArgs([]string{"search", "foo"}) - err := command.Execute() - assert.NoError(t, err) - })) -} - -func TestPrintVersion(t *testing.T) { - // Register test instance singleton - pkg.NewTestRegistryInstance(io.TestDirectory(t)) - command := cmd() - command.SetArgs([]string{"version"}) - err := command.Execute() - assert.NoError(t, err) -} - -func Test_flagSet(t *testing.T) { - assert.NotNil(t, flagSet()) -} - -func TestNewRegistryEngine(t *testing.T) { - // Register test instance singleton - pkg.NewTestRegistryInstance(io.TestDirectory(t)) - t.Run("instance", func(t *testing.T) { - assert.NotNil(t, NewRegistryEngine()) - }) - - t.Run("configuration", func(t *testing.T) { - e := NewRegistryEngine() - cfg := core.NutsConfig() - cfg.RegisterFlags(e.Cmd, e) - assert.NoError(t, cfg.InjectIntoEngine(e)) - }) -} - -func withMock(test func(t *testing.T, client *mock.MockRegistryClient)) func(t *testing.T) { - return func(t *testing.T) { - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - registryClient := mock.NewMockRegistryClient(mockCtrl) - registryClientCreator = func() pkg.RegistryClient { - return registryClient - } - test(t, registryClient) - } -} - -func generateCertificate() map[string]interface{} { - privateKey, _ := rsa.GenerateKey(rand.Reader, 1024) - certAsBytes := test.GenerateCertificateEx(time.Now(), 1, privateKey) - certificate, _ := x509.ParseCertificate(certAsBytes) - certAsJWK, _ := cert.CertificateToJWK(certificate) - certAsMap, _ := cert.JwkToMap(certAsJWK) - return certAsMap -} - -func configureIdentity() { - os.Setenv("NUTS_IDENTITY", test.VendorID("4").String()) - core.NutsConfig().Load(&cobra.Command{}) -} diff --git a/go.mod b/go.mod index 3358a79f0d..7b67491d19 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,12 @@ require ( 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-20210114144203-c658fb4d0f65 // indirect - github.com/nuts-foundation/nuts-network v0.16.0 // indirect + github.com/nuts-foundation/go-did v0.0.0-20210114144203-c658fb4d0f65 + github.com/nuts-foundation/nuts-crypto v0.16.0 + github.com/nuts-foundation/nuts-go-core v0.16.0 + github.com/nuts-foundation/nuts-go-test v0.16.0 + github.com/nuts-foundation/nuts-network v0.16.0 + github.com/nuts-foundation/nuts-registry v0.16.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.7.1 github.com/sirupsen/logrus v1.7.0 diff --git a/go.sum b/go.sum index a2573e3840..a5cc6f53a3 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc 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/cyberphone/json-canonicalization v0.0.0-20200417180520-cd6247b5f11e h1:v7sORZGlE3dVwkSPHuG4RNsxT1bOyHXw09A2BJ8dxvQ= +github.com/cyberphone/json-canonicalization v0.0.0-20200417180520-cd6247b5f11e/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= 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= @@ -85,6 +87,7 @@ github.com/deepmap/oapi-codegen v1.4.1/go.mod h1:1jY0YDxfBF3tXk1u3sARJMSUJa9wV0U 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= @@ -104,6 +107,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF 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= @@ -119,6 +123,7 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 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= @@ -129,6 +134,7 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV 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= @@ -174,6 +180,8 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI 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/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0= +github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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= @@ -246,6 +254,7 @@ 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= @@ -275,6 +284,7 @@ github.com/labstack/echo/v4 v4.1.17 h1:PQIBaRplyRy3OjwILGkPg89JRtH2x5bssi59G2EL3 github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/leodido/go-urn v1.2.1-0.20201207182545-66fa6ee96763/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lestrrat-go/backoff/v2 v2.0.3 h1:2ABaTa5ifB1L90aoRMjaPa97p0WzzVe93Vggv8oZftw= github.com/lestrrat-go/backoff/v2 v2.0.3/go.mod h1:mU93bMXuG27/Y5erI5E9weqavpTX5qiVFZI4uXAX0xk= github.com/lestrrat-go/httpcc v0.0.0-20210101035852-e7e8fea419e3 h1:e52qvXxpJPV/Kb2ovtuYgcRFjNmf9ntcn8BPIbpRM4k= @@ -286,6 +296,7 @@ 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= @@ -293,6 +304,7 @@ 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= @@ -347,9 +359,12 @@ github.com/nuts-foundation/nuts-crypto v0.16.0 h1:PzNDcoawuGxl4KRoD3nKnz+1sFiMbE 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/nuts-foundation/nuts-registry v0.16.0 h1:pcZVJExFoDl6axgG7WiIVmGhl8e1J1nP0VyUpZSucY4= +github.com/nuts-foundation/nuts-registry v0.16.0/go.mod h1:thE+6wah6xy6tMe/RRRY69ZWR4OiEVTD4MAVnLhdJbs= 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= @@ -362,6 +377,8 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ 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/pelletier/go-toml v1.5.0 h1:5BakdOZdtKJ1FFk6QdL8iSGrMWsXgchNJcrnarjbmJQ= +github.com/pelletier/go-toml v1.5.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= 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= @@ -666,6 +683,7 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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= diff --git a/did/api/v1/api.go b/registry/api/v1/api.go similarity index 95% rename from did/api/v1/api.go rename to registry/api/v1/api.go index 7390847276..75426e7887 100644 --- a/did/api/v1/api.go +++ b/registry/api/v1/api.go @@ -21,12 +21,12 @@ package v1 import ( "github.com/labstack/echo/v4" - "github.com/nuts-foundation/nuts-node/did" + "github.com/nuts-foundation/nuts-node/registry" ) // ApiWrapper is needed to connect the implementation to the echo ServiceWrapper type ApiWrapper struct { - R did.Store + R registry.Store } func (a ApiWrapper) SearchDID(ctx echo.Context, params SearchDIDParams) error { diff --git a/did/api/v1/client.go b/registry/api/v1/client.go similarity index 100% rename from did/api/v1/client.go rename to registry/api/v1/client.go diff --git a/did/api/v1/generated.go b/registry/api/v1/generated.go similarity index 100% rename from did/api/v1/generated.go rename to registry/api/v1/generated.go diff --git a/registry/config.go b/registry/config.go new file mode 100644 index 0000000000..4f91b912a0 --- /dev/null +++ b/registry/config.go @@ -0,0 +1,49 @@ +package registry + +// ConfDataDir is the config name for specifiying the data location of the requiredFiles +const ConfDataDir = "datadir" + +// ConfMode is the config name for the engine mode, server or client +const ConfMode = "mode" + +// ConfAddress is the config name for the http server/client address +const ConfAddress = "address" + +// ConfSyncMode is the config name for the used SyncMode +const ConfSyncMode = "syncMode" + +// ConfSyncAddress is the config name for the remote address used to fetch updated registry files +const ConfSyncAddress = "syncAddress" + +// ConfSyncInterval is the config name for the interval in minutes to look for new registry files online +const ConfSyncInterval = "syncInterval" + +// ConfOrganisationCertificateValidity is the config name for the number of days organisation certificates are valid +const ConfOrganisationCertificateValidity = "organisationCertificateValidity" + +// ConfVendorCACertificateValidity is the config name for the number of days vendor CA certificates are valid +const ConfVendorCACertificateValidity = "vendorCACertificateValidity" + +// ConfClientTimeout is the time-out for the client in seconds (e.g. when using the CLI). +const ConfClientTimeout = "clientTimeout" + +// ModuleName == Registry +const ModuleName = "Registry" + +// Config holds the config for the Registry engine +type Config struct { + Mode string + Datadir string + Address string + ClientTimeout int +} + +// DefaultRegistryConfig returns a fresh Config filled with default values +func DefaultRegistryConfig() Config { + return Config{ + Datadir: "./data", + Address: "localhost:1323", + ClientTimeout: 10, + } +} + diff --git a/did/engine/engine.go b/registry/engine/engine.go similarity index 79% rename from did/engine/engine.go rename to registry/engine/engine.go index 7c9dc1b39f..00ade73356 100644 --- a/did/engine/engine.go +++ b/registry/engine/engine.go @@ -23,21 +23,21 @@ import ( "fmt" "github.com/nuts-foundation/nuts-network/pkg" - "github.com/nuts-foundation/nuts-node/did/logging" + "github.com/nuts-foundation/nuts-node/registry/logging" "github.com/nuts-foundation/nuts-node/core" - "github.com/nuts-foundation/nuts-node/did" - api "github.com/nuts-foundation/nuts-node/did/api/v1" + "github.com/nuts-foundation/nuts-node/registry" + api "github.com/nuts-foundation/nuts-node/registry/api/v1" "github.com/spf13/cobra" "github.com/spf13/pflag" ) // registryClientCreator is a variable to aid testability -var registryClientCreator = did.RegistryInstance() +var registryClientCreator = registry.RegistryInstance() // NewRegistryEngine returns the core definition for the registry func NewRegistryEngine() *core.Engine { - r := did.RegistryInstance() + r := registry.RegistryInstance() return &core.Engine{ Cmd: cmd(), @@ -58,11 +58,11 @@ func NewRegistryEngine() *core.Engine { func flagSet() *pflag.FlagSet { flagSet := pflag.NewFlagSet("registry", pflag.ContinueOnError) - defs := did.DefaultRegistryConfig() - flagSet.String(did.ConfDataDir, defs.Datadir, fmt.Sprintf("Location of data files, default: %s", defs.Datadir)) - flagSet.String(did.ConfMode, defs.Mode, fmt.Sprintf("server or client, when client it uses the HttpClient, default: %s", defs.Mode)) - flagSet.String(did.ConfAddress, defs.Address, fmt.Sprintf("Interface and port for http server to bind to, default: %s", defs.Address)) - flagSet.Int(did.ConfClientTimeout, defs.ClientTimeout, fmt.Sprintf("Time-out for the client in seconds (e.g. when using the CLI), default: %d", defs.ClientTimeout)) + defs := registry.DefaultRegistryConfig() + flagSet.String(registry.ConfDataDir, defs.Datadir, fmt.Sprintf("Location of data files, default: %s", defs.Datadir)) + flagSet.String(registry.ConfMode, defs.Mode, fmt.Sprintf("server or client, when client it uses the HttpClient, default: %s", defs.Mode)) + flagSet.String(registry.ConfAddress, defs.Address, fmt.Sprintf("Interface and port for http server to bind to, default: %s", defs.Address)) + flagSet.Int(registry.ConfClientTimeout, defs.ClientTimeout, fmt.Sprintf("Time-out for the client in seconds (e.g. when using the CLI), default: %d", defs.ClientTimeout)) return flagSet } diff --git a/registry/engine/engine_test.go b/registry/engine/engine_test.go new file mode 100644 index 0000000000..179e62bc86 --- /dev/null +++ b/registry/engine/engine_test.go @@ -0,0 +1,57 @@ +/* + * Nuts registry + * Copyright (C) 2020. 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 ( + "testing" + + "github.com/stretchr/testify/assert" + + core "github.com/nuts-foundation/nuts-node/core" +) + +func TestSearchOrg(t *testing.T) { + // Register test instance singleton +} + +func TestPrintVersion(t *testing.T) { + // Register test instance singleton + command := cmd() + command.SetArgs([]string{"version"}) + err := command.Execute() + assert.NoError(t, err) +} + +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, NewRegistryEngine()) + }) + + t.Run("configuration", func(t *testing.T) { + e := NewRegistryEngine() + cfg := core.NutsConfig() + cfg.RegisterFlags(e.Cmd, e) + assert.NoError(t, cfg.InjectIntoEngine(e)) + }) +} diff --git a/did/logging/log.go b/registry/logging/log.go similarity index 100% rename from did/logging/log.go rename to registry/logging/log.go diff --git a/did/logging/log_test.go b/registry/logging/log_test.go similarity index 100% rename from did/logging/log_test.go rename to registry/logging/log_test.go diff --git a/did/network/ambassador.go b/registry/network/ambassador.go similarity index 97% rename from did/network/ambassador.go rename to registry/network/ambassador.go index 74cdb28de4..2295ce89c5 100644 --- a/did/network/ambassador.go +++ b/registry/network/ambassador.go @@ -24,7 +24,7 @@ import ( network "github.com/nuts-foundation/nuts-network/pkg" "github.com/nuts-foundation/nuts-network/pkg/model" "github.com/nuts-foundation/nuts-node/crypto" - "github.com/nuts-foundation/nuts-node/did/logging" + "github.com/nuts-foundation/nuts-node/registry/logging" ) const documentType = "nuts.registry-event" diff --git a/did/network/mock.go b/registry/network/mock.go similarity index 100% rename from did/network/mock.go rename to registry/network/mock.go diff --git a/did/store.go b/registry/registry.go similarity index 53% rename from did/store.go rename to registry/registry.go index ab782052a4..9c9cad105d 100644 --- a/did/store.go +++ b/registry/registry.go @@ -17,93 +17,31 @@ * */ -package did +// Package registry 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 registry import ( "sync" "time" "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/nuts-network/pkg/model" - "github.com/nuts-foundation/nuts-node/did/logging" "github.com/sirupsen/logrus" + "github.com/nuts-foundation/nuts-node/registry/logging" + "github.com/nuts-foundation/nuts-network/pkg" - "github.com/nuts-foundation/nuts-node/did/network" - "github.com/nuts-foundation/nuts-node/core" + "github.com/nuts-foundation/nuts-node/registry/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" ) -// ConfDataDir is the config name for specifiying the data location of the requiredFiles -const ConfDataDir = "datadir" - -// ConfMode is the config name for the engine mode, server or client -const ConfMode = "mode" - -// ConfAddress is the config name for the http server/client address -const ConfAddress = "address" - -// ConfSyncMode is the config name for the used SyncMode -const ConfSyncMode = "syncMode" - -// ConfSyncAddress is the config name for the remote address used to fetch updated registry files -const ConfSyncAddress = "syncAddress" - -// ConfSyncInterval is the config name for the interval in minutes to look for new registry files online -const ConfSyncInterval = "syncInterval" - -// ConfOrganisationCertificateValidity is the config name for the number of days organisation certificates are valid -const ConfOrganisationCertificateValidity = "organisationCertificateValidity" - -// ConfVendorCACertificateValidity is the config name for the number of days vendor CA certificates are valid -const ConfVendorCACertificateValidity = "vendorCACertificateValidity" - -// ConfClientTimeout is the time-out for the client in seconds (e.g. when using the CLI). -const ConfClientTimeout = "clientTimeout" - -// ModuleName == Registry -const ModuleName = "Registry" - -// ReloadRegistryIdleTimeout defines the cooling down period after receiving a file watcher notification, before -// the registry is reloaded (from disk). -var ReloadRegistryIdleTimeout time.Duration - -// Store is the interface for the low level DID operations. -type Store interface { - // Search searches for DID documents that match the given conditions; - // - onlyOwn: only return documents which contain a verificationMethod which' private key is present in this node. - // - tags: only return documents that match ALL of the given tags. - // If something goes wrong an error is returned. - Search(onlyOwn bool, tags []string) ([]did.Document, error) - // Create creates a new DID document and returns it. If something goes wrong an error is returned. - Create() (*did.Document, error) - // Get returns the DID document using on the given DID or nil if not found. If something goes wrong an error is returned. - Get(DID did.DID) (*did.Document, *DocumentMetadata, error) - // GetByTag gets a DID document using the given tag or nil if not found. If multiple documents match the given tag - // or something else goes wrong, an error is returned. - GetByTag(tag string) (*did.Document, *DocumentMetadata, error) - // Update replaces the DID document identified by DID with the nextVersion if the given hash matches the current valid DID document hash. - Update(DID did.DID, hash []byte, nextVersion did.Document) (*did.Document, error) - // Tag replaces all tags on a DID document given the DID. - Tag(DID did.DID, tags []string) error -} - -// 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 model.Hash `json:"originJwsHash"` - // Hash of DID document bytes. Is equal to payloadHash in network layer. - Hash string `json:"hash"` - // Tags of the DID document. - Tags []string `json:"tags,omitempty"` -} //type StoreWrapper struct { // networkClient networkPkg.NetworkClient @@ -114,22 +52,6 @@ type DocumentMetadata struct { // return &StoreWrapper(store: store) //} -// Config holds the config -type Config struct { - Mode string - Datadir string - Address string - ClientTimeout int -} - -func DefaultRegistryConfig() Config { - return Config{ - Datadir: "./data", - Address: "localhost:1323", - ClientTimeout: 10, - } -} - // Registry holds the config and Db reference type Registry struct { Config Config @@ -146,6 +68,10 @@ type Registry struct { var instance *Registry var oneRegistry sync.Once +// ReloadRegistryIdleTimeout defines the cooling down period after receiving a file watcher notification, before +// the registry is reloaded (from disk). +var ReloadRegistryIdleTimeout time.Duration + func init() { ReloadRegistryIdleTimeout = 3 * time.Second } diff --git a/registry/types.go b/registry/types.go new file mode 100644 index 0000000000..f0b3dfd242 --- /dev/null +++ b/registry/types.go @@ -0,0 +1,42 @@ +package registry + +import ( + "time" + + "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-network/pkg/model" +) + +// Store is the interface for the low level DID operations. +type Store interface { + // Search searches for DID documents that match the given conditions; + // - onlyOwn: only return documents which contain a verificationMethod which' private key is present in this node. + // - tags: only return documents that match ALL of the given tags. + // If something goes wrong an error is returned. + Search(onlyOwn bool, tags []string) ([]did.Document, error) + // Create creates a new DID document and returns it. If something goes wrong an error is returned. + Create() (*did.Document, error) + // Get returns the DID document using on the given DID or nil if not found. If something goes wrong an error is returned. + Get(DID did.DID) (*did.Document, *DocumentMetadata, error) + // GetByTag gets a DID document using the given tag or nil if not found. If multiple documents match the given tag + // or something else goes wrong, an error is returned. + GetByTag(tag string) (*did.Document, *DocumentMetadata, error) + // Update replaces the DID document identified by DID with the nextVersion if the given hash matches the current valid DID document hash. + Update(DID did.DID, hash []byte, nextVersion did.Document) (*did.Document, error) + // Tag replaces all tags on a DID document given the DID. + Tag(DID did.DID, tags []string) error +} + +// 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 model.Hash `json:"originJwsHash"` + // Hash of DID document bytes. Is equal to payloadHash in network layer. + Hash string `json:"hash"` + // Tags of the DID document. + Tags []string `json:"tags,omitempty"` +} From 0a236de77b4d1e6200529217a1fbd193e75d76f9 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Tue, 19 Jan 2021 11:27:07 +0100 Subject: [PATCH 04/54] Rename registry to vdr --- registry/types.go | 42 ------------------ {registry => vdr}/api/v1/api.go | 4 +- {registry => vdr}/api/v1/client.go | 0 {registry => vdr}/api/v1/generated.go | 0 {registry => vdr}/config.go | 2 +- {registry => vdr}/engine/engine.go | 20 ++++----- {registry => vdr}/engine/engine_test.go | 0 {registry => vdr}/logging/log.go | 0 {registry => vdr}/logging/log_test.go | 0 {registry => vdr}/network/ambassador.go | 2 +- {registry => vdr}/network/mock.go | 0 vdr/types.go | 57 +++++++++++++++++++++++++ registry/registry.go => vdr/vdr.go | 9 ++-- 13 files changed, 76 insertions(+), 60 deletions(-) delete mode 100644 registry/types.go rename {registry => vdr}/api/v1/api.go (95%) rename {registry => vdr}/api/v1/client.go (100%) rename {registry => vdr}/api/v1/generated.go (100%) rename {registry => vdr}/config.go (98%) rename {registry => vdr}/engine/engine.go (79%) rename {registry => vdr}/engine/engine_test.go (100%) rename {registry => vdr}/logging/log.go (100%) rename {registry => vdr}/logging/log_test.go (100%) rename {registry => vdr}/network/ambassador.go (97%) rename {registry => vdr}/network/mock.go (100%) create mode 100644 vdr/types.go rename registry/registry.go => vdr/vdr.go (94%) diff --git a/registry/types.go b/registry/types.go deleted file mode 100644 index f0b3dfd242..0000000000 --- a/registry/types.go +++ /dev/null @@ -1,42 +0,0 @@ -package registry - -import ( - "time" - - "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/nuts-network/pkg/model" -) - -// Store is the interface for the low level DID operations. -type Store interface { - // Search searches for DID documents that match the given conditions; - // - onlyOwn: only return documents which contain a verificationMethod which' private key is present in this node. - // - tags: only return documents that match ALL of the given tags. - // If something goes wrong an error is returned. - Search(onlyOwn bool, tags []string) ([]did.Document, error) - // Create creates a new DID document and returns it. If something goes wrong an error is returned. - Create() (*did.Document, error) - // Get returns the DID document using on the given DID or nil if not found. If something goes wrong an error is returned. - Get(DID did.DID) (*did.Document, *DocumentMetadata, error) - // GetByTag gets a DID document using the given tag or nil if not found. If multiple documents match the given tag - // or something else goes wrong, an error is returned. - GetByTag(tag string) (*did.Document, *DocumentMetadata, error) - // Update replaces the DID document identified by DID with the nextVersion if the given hash matches the current valid DID document hash. - Update(DID did.DID, hash []byte, nextVersion did.Document) (*did.Document, error) - // Tag replaces all tags on a DID document given the DID. - Tag(DID did.DID, tags []string) error -} - -// 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 model.Hash `json:"originJwsHash"` - // Hash of DID document bytes. Is equal to payloadHash in network layer. - Hash string `json:"hash"` - // Tags of the DID document. - Tags []string `json:"tags,omitempty"` -} diff --git a/registry/api/v1/api.go b/vdr/api/v1/api.go similarity index 95% rename from registry/api/v1/api.go rename to vdr/api/v1/api.go index 75426e7887..e37d8a0456 100644 --- a/registry/api/v1/api.go +++ b/vdr/api/v1/api.go @@ -21,12 +21,12 @@ package v1 import ( "github.com/labstack/echo/v4" - "github.com/nuts-foundation/nuts-node/registry" + "github.com/nuts-foundation/nuts-node/vdr" ) // ApiWrapper is needed to connect the implementation to the echo ServiceWrapper type ApiWrapper struct { - R registry.Store + R vdr.Store } func (a ApiWrapper) SearchDID(ctx echo.Context, params SearchDIDParams) error { diff --git a/registry/api/v1/client.go b/vdr/api/v1/client.go similarity index 100% rename from registry/api/v1/client.go rename to vdr/api/v1/client.go diff --git a/registry/api/v1/generated.go b/vdr/api/v1/generated.go similarity index 100% rename from registry/api/v1/generated.go rename to vdr/api/v1/generated.go diff --git a/registry/config.go b/vdr/config.go similarity index 98% rename from registry/config.go rename to vdr/config.go index 4f91b912a0..468b10a8d8 100644 --- a/registry/config.go +++ b/vdr/config.go @@ -1,4 +1,4 @@ -package registry +package vdr // ConfDataDir is the config name for specifiying the data location of the requiredFiles const ConfDataDir = "datadir" diff --git a/registry/engine/engine.go b/vdr/engine/engine.go similarity index 79% rename from registry/engine/engine.go rename to vdr/engine/engine.go index 00ade73356..dd83b81025 100644 --- a/registry/engine/engine.go +++ b/vdr/engine/engine.go @@ -23,21 +23,21 @@ import ( "fmt" "github.com/nuts-foundation/nuts-network/pkg" - "github.com/nuts-foundation/nuts-node/registry/logging" + "github.com/nuts-foundation/nuts-node/vdr/logging" "github.com/nuts-foundation/nuts-node/core" - "github.com/nuts-foundation/nuts-node/registry" - api "github.com/nuts-foundation/nuts-node/registry/api/v1" + "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" ) // registryClientCreator is a variable to aid testability -var registryClientCreator = registry.RegistryInstance() +var registryClientCreator = vdr.RegistryInstance() // NewRegistryEngine returns the core definition for the registry func NewRegistryEngine() *core.Engine { - r := registry.RegistryInstance() + r := vdr.RegistryInstance() return &core.Engine{ Cmd: cmd(), @@ -58,11 +58,11 @@ func NewRegistryEngine() *core.Engine { func flagSet() *pflag.FlagSet { flagSet := pflag.NewFlagSet("registry", pflag.ContinueOnError) - defs := registry.DefaultRegistryConfig() - flagSet.String(registry.ConfDataDir, defs.Datadir, fmt.Sprintf("Location of data files, default: %s", defs.Datadir)) - flagSet.String(registry.ConfMode, defs.Mode, fmt.Sprintf("server or client, when client it uses the HttpClient, default: %s", defs.Mode)) - flagSet.String(registry.ConfAddress, defs.Address, fmt.Sprintf("Interface and port for http server to bind to, default: %s", defs.Address)) - flagSet.Int(registry.ConfClientTimeout, defs.ClientTimeout, fmt.Sprintf("Time-out for the client in seconds (e.g. when using the CLI), default: %d", defs.ClientTimeout)) + defs := vdr.DefaultRegistryConfig() + flagSet.String(vdr.ConfDataDir, defs.Datadir, fmt.Sprintf("Location of data files, default: %s", defs.Datadir)) + flagSet.String(vdr.ConfMode, defs.Mode, fmt.Sprintf("server or client, when client it uses the HttpClient, default: %s", defs.Mode)) + flagSet.String(vdr.ConfAddress, defs.Address, fmt.Sprintf("Interface and port for http server to bind to, default: %s", defs.Address)) + 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 } diff --git a/registry/engine/engine_test.go b/vdr/engine/engine_test.go similarity index 100% rename from registry/engine/engine_test.go rename to vdr/engine/engine_test.go diff --git a/registry/logging/log.go b/vdr/logging/log.go similarity index 100% rename from registry/logging/log.go rename to vdr/logging/log.go diff --git a/registry/logging/log_test.go b/vdr/logging/log_test.go similarity index 100% rename from registry/logging/log_test.go rename to vdr/logging/log_test.go diff --git a/registry/network/ambassador.go b/vdr/network/ambassador.go similarity index 97% rename from registry/network/ambassador.go rename to vdr/network/ambassador.go index 2295ce89c5..014153cce1 100644 --- a/registry/network/ambassador.go +++ b/vdr/network/ambassador.go @@ -24,7 +24,7 @@ import ( network "github.com/nuts-foundation/nuts-network/pkg" "github.com/nuts-foundation/nuts-network/pkg/model" "github.com/nuts-foundation/nuts-node/crypto" - "github.com/nuts-foundation/nuts-node/registry/logging" + "github.com/nuts-foundation/nuts-node/vdr/logging" ) const documentType = "nuts.registry-event" diff --git a/registry/network/mock.go b/vdr/network/mock.go similarity index 100% rename from registry/network/mock.go rename to vdr/network/mock.go diff --git a/vdr/types.go b/vdr/types.go new file mode 100644 index 0000000000..8094bb1cd1 --- /dev/null +++ b/vdr/types.go @@ -0,0 +1,57 @@ +package vdr + +import ( + "errors" + "time" + + "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-network/pkg/model" +) + +var ErrUpdateOnOutdatedData = errors.New("could not update outdated document") +// ErrInvalidDID The DID supplied to the DID resolution function does not conform to valid syntax.0 +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") + +// DocReader is the interface that groups all the DID Document read methods +// Get returns the DID document using on the given DID or ErrNotFound if not found. +// If something goes wrong an error is returned. +type DocReader interface { + Get(DID did.DID) (*did.Document, *DocumentMetadata, error) +} + +// DocWriter is the interface that groups al the DID Document write methods +type DocWriter interface { + // Create creates a new DID document and returns it. If something goes wrong an error is returned. + Create() (*did.Document, error) + + // Update replaces the DID document identified by DID with the nextVersion + // To prevent updating state 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(DID did.DID, hash []byte, nextVersion did.Document) (*did.Document, error) +} + +// Store is the interface that groups all operations on DID documents. +type Store interface { + DocReader + DocWriter +} + +// 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 model.Hash `json:"originJwsHash"` + // Hash of DID document bytes. Is equal to payloadHash in network layer. + Hash string `json:"hash"` + // Tags of the DID document. + Tags []string `json:"tags,omitempty"` +} diff --git a/registry/registry.go b/vdr/vdr.go similarity index 94% rename from registry/registry.go rename to vdr/vdr.go index 9c9cad105d..a010d40d8d 100644 --- a/registry/registry.go +++ b/vdr/vdr.go @@ -17,10 +17,11 @@ * */ -// Package registry provides primitives for storing and working with Nuts DID based identities. +// 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 registry +package vdr import ( "sync" @@ -29,11 +30,11 @@ import ( "github.com/nuts-foundation/go-did" "github.com/sirupsen/logrus" - "github.com/nuts-foundation/nuts-node/registry/logging" + "github.com/nuts-foundation/nuts-node/vdr/logging" "github.com/nuts-foundation/nuts-network/pkg" - "github.com/nuts-foundation/nuts-node/registry/network" + "github.com/nuts-foundation/nuts-node/vdr/network" networkClient "github.com/nuts-foundation/nuts-network/client" networkPkg "github.com/nuts-foundation/nuts-network/pkg" From 4f8cfef0a38e8d70e3c1ad1c801bba2208520440 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Tue, 19 Jan 2021 11:41:05 +0100 Subject: [PATCH 05/54] Resolver with metdadata --- vdr/types.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/vdr/types.go b/vdr/types.go index 8094bb1cd1..9c571000d1 100644 --- a/vdr/types.go +++ b/vdr/types.go @@ -15,17 +15,22 @@ var ErrInvalidDID = errors.New("invalid did syntax") 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") +var ErrDIDAlreadyExists = errors.New("did document already exists") // DocReader is the interface that groups all the DID Document read methods // Get returns the DID document using on the given DID or ErrNotFound if not found. +// If metadata is provided the the result is filtered or scoped on that meta data +// If metadata is provided the latest version is returned // If something goes wrong an error is returned. -type DocReader interface { - Get(DID did.DID) (*did.Document, *DocumentMetadata, error) +type DocResolver interface { + Resolve(DID did.DID, metadata *ResolveMetaData) (*did.Document, *DocumentMetadata, error) } // DocWriter is the interface that groups al the DID Document write methods type DocWriter interface { - // Create creates a new DID document and returns it. If something goes wrong an error is returned. + // Create creates a new DID document and returns it. + // If the DID already exists, an ErrDIDAlreadyExists gets returned + // If something goes wrong an error is returned. Create() (*did.Document, error) // Update replaces the DID document identified by DID with the nextVersion @@ -38,7 +43,7 @@ type DocWriter interface { // Store is the interface that groups all operations on DID documents. type Store interface { - DocReader + DocResolver DocWriter } @@ -52,6 +57,13 @@ type DocumentMetadata struct { OriginJWSHash model.Hash `json:"originJwsHash"` // Hash of DID document bytes. Is equal to payloadHash in network layer. Hash string `json:"hash"` - // Tags of the DID document. - Tags []string `json:"tags,omitempty"` } + +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 []byte + // Allow DIDs which are deactivated + AllowDeactivated bool +} \ No newline at end of file From 16eb58811294014ffd934f9ba90c50634100e9e0 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Tue, 19 Jan 2021 12:01:44 +0100 Subject: [PATCH 06/54] New DocUpdate interface --- vdr/types.go | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/vdr/types.go b/vdr/types.go index 9c571000d1..b77aae920d 100644 --- a/vdr/types.go +++ b/vdr/types.go @@ -15,29 +15,40 @@ var ErrInvalidDID = errors.New("invalid did syntax") 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") -var ErrDIDAlreadyExists = errors.New("did document already exists") +// ErrDIDAlreadyExists +var ErrDIDAlreadyExists = errors.New("did document already exists in the store") // DocReader is the interface that groups all the DID Document read methods // Get returns the DID document using on the given DID or ErrNotFound if not found. // If metadata is provided the the result is filtered or scoped on that meta data -// If metadata is provided the latest version is returned +// If metadata is not provided the latest version is returned // If something goes wrong an error is returned. type DocResolver interface { Resolve(DID did.DID, metadata *ResolveMetaData) (*did.Document, *DocumentMetadata, error) } +// 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 +type DocCreator interface { + Create() (*did.Document, error) +} + // DocWriter is the interface that groups al the DID Document write methods type DocWriter interface { - // Create creates a new DID document and returns it. - // If the DID already exists, an ErrDIDAlreadyExists gets returned - // If something goes wrong an error is returned. - Create() (*did.Document, error) + // Write writes new DID Document. + // Returns ErrDIDAlreadyExists when DID already exists + // When a document already exists, the Update should be used instead + Write(DID did.Document) error +} - // Update replaces the DID document identified by DID with the nextVersion - // To prevent updating state 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 replaces the DID document identified by DID with the nextVersion +// To prevent updating state 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 +type DocUpdater interface { Update(DID did.DID, hash []byte, nextVersion did.Document) (*did.Document, error) } @@ -45,6 +56,8 @@ type DocWriter interface { type Store interface { DocResolver DocWriter + DocCreator + DocUpdater } // DocumentMetadata holds the metadata of a DID document From a806aada0510447878ba5838af3fb77d5fb4d251 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Tue, 19 Jan 2021 12:09:15 +0100 Subject: [PATCH 07/54] Change visibility of store --- vdr/api/v1/api.go | 2 +- vdr/types.go | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/vdr/api/v1/api.go b/vdr/api/v1/api.go index e37d8a0456..3e0c0e02a6 100644 --- a/vdr/api/v1/api.go +++ b/vdr/api/v1/api.go @@ -26,7 +26,7 @@ import ( // ApiWrapper is needed to connect the implementation to the echo ServiceWrapper type ApiWrapper struct { - R vdr.Store + R vdr.VDR } func (a ApiWrapper) SearchDID(ctx echo.Context, params SearchDIDParams) error { diff --git a/vdr/types.go b/vdr/types.go index b77aae920d..5d82a6a51b 100644 --- a/vdr/types.go +++ b/vdr/types.go @@ -52,10 +52,16 @@ type DocUpdater interface { Update(DID did.DID, hash []byte, nextVersion did.Document) (*did.Document, error) } -// Store is the interface that groups all operations on DID documents. -type Store interface { +// 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 } @@ -72,6 +78,7 @@ type DocumentMetadata struct { Hash string `json:"hash"` } +// ResolveMetaData contains metadata for the resolver. type ResolveMetaData struct { // Resolve the version which is valid at this time ResolveTime *time.Time From 9da1b3328590c92d6d3907800c13adef8d1278d4 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Tue, 19 Jan 2021 12:10:10 +0100 Subject: [PATCH 08/54] move crypto mock --- crypto/{mock/mock_crypto.go => mock.go} | 0 docs/pages/development.rst | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename crypto/{mock/mock_crypto.go => mock.go} (100%) diff --git a/crypto/mock/mock_crypto.go b/crypto/mock.go similarity index 100% rename from crypto/mock/mock_crypto.go rename to crypto/mock.go diff --git a/docs/pages/development.rst b/docs/pages/development.rst index 079023aa65..e69164122a 100644 --- a/docs/pages/development.rst +++ b/docs/pages/development.rst @@ -38,7 +38,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 + mockgen -destination=crypto/mock.go -package=crypto -source=crypto/interface.go KeyStore README ****** From e99111f35cc2248803a91fcae80b455db36e8ead Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Tue, 19 Jan 2021 12:11:47 +0100 Subject: [PATCH 09/54] pkg error in crypto mock --- crypto/mock.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crypto/mock.go b/crypto/mock.go index 2d0a43db0e..de9d7f8650 100644 --- a/crypto/mock.go +++ b/crypto/mock.go @@ -2,12 +2,11 @@ // Source: crypto/interface.go // Package mock is a generated GoMock package. -package mock +package crypto import ( crypto "crypto" gomock "github.com/golang/mock/gomock" - crypto0 "github.com/nuts-foundation/nuts-node/crypto" reflect "reflect" ) @@ -35,7 +34,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) From 44cb1a5f418acfd149e7e458070c9a679ccd3f70 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Tue, 19 Jan 2021 14:43:03 +0100 Subject: [PATCH 10/54] added version-less in memory store --- go.mod | 4 -- vdr/api/v1/api.go | 8 ++-- vdr/api/v1/client.go | 4 +- vdr/engine/engine.go | 4 +- vdr/engine/engine_test.go | 4 +- vdr/network/ambassador.go | 4 +- vdr/types.go | 89 --------------------------------------- vdr/vdr.go | 33 +++------------ 8 files changed, 18 insertions(+), 132 deletions(-) delete mode 100644 vdr/types.go diff --git a/go.mod b/go.mod index 7b67491d19..c2e652ca06 100644 --- a/go.mod +++ b/go.mod @@ -9,11 +9,7 @@ require ( github.com/lestrrat-go/jwx v1.0.7 github.com/magiconair/properties v1.8.4 github.com/nuts-foundation/go-did v0.0.0-20210114144203-c658fb4d0f65 - github.com/nuts-foundation/nuts-crypto v0.16.0 - github.com/nuts-foundation/nuts-go-core v0.16.0 - github.com/nuts-foundation/nuts-go-test v0.16.0 github.com/nuts-foundation/nuts-network v0.16.0 - github.com/nuts-foundation/nuts-registry v0.16.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.7.1 github.com/sirupsen/logrus v1.7.0 diff --git a/vdr/api/v1/api.go b/vdr/api/v1/api.go index 3e0c0e02a6..bc52ed91a9 100644 --- a/vdr/api/v1/api.go +++ b/vdr/api/v1/api.go @@ -1,6 +1,6 @@ /* - * Nuts registry - * 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 @@ -21,12 +21,12 @@ package v1 import ( "github.com/labstack/echo/v4" - "github.com/nuts-foundation/nuts-node/vdr" + "github.com/nuts-foundation/nuts-node/vdr/types" ) // ApiWrapper is needed to connect the implementation to the echo ServiceWrapper type ApiWrapper struct { - R vdr.VDR + R types.VDR } func (a ApiWrapper) SearchDID(ctx echo.Context, params SearchDIDParams) error { diff --git a/vdr/api/v1/client.go b/vdr/api/v1/client.go index bad185a391..0d54bd620a 100644 --- a/vdr/api/v1/client.go +++ b/vdr/api/v1/client.go @@ -1,6 +1,6 @@ /* - * Nuts registry - * 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/vdr/engine/engine.go b/vdr/engine/engine.go index dd83b81025..34ba712c7d 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -1,6 +1,6 @@ /* - * Nuts registry - * 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/vdr/engine/engine_test.go b/vdr/engine/engine_test.go index 179e62bc86..5c4e380d6c 100644 --- a/vdr/engine/engine_test.go +++ b/vdr/engine/engine_test.go @@ -1,6 +1,6 @@ /* - * Nuts registry - * 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/vdr/network/ambassador.go b/vdr/network/ambassador.go index 014153cce1..994f252c0d 100644 --- a/vdr/network/ambassador.go +++ b/vdr/network/ambassador.go @@ -1,6 +1,6 @@ /* - * Nuts registry - * 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/vdr/types.go b/vdr/types.go deleted file mode 100644 index 5d82a6a51b..0000000000 --- a/vdr/types.go +++ /dev/null @@ -1,89 +0,0 @@ -package vdr - -import ( - "errors" - "time" - - "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/nuts-network/pkg/model" -) - -var ErrUpdateOnOutdatedData = errors.New("could not update outdated document") -// ErrInvalidDID The DID supplied to the DID resolution function does not conform to valid syntax.0 -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 -var ErrDIDAlreadyExists = errors.New("did document already exists in the store") - -// DocReader is the interface that groups all the DID Document read methods -// Get returns the DID document using on the given DID or ErrNotFound if not found. -// If metadata is provided the the result is filtered or scoped on that meta data -// If metadata is not provided the latest version is returned -// If something goes wrong an error is returned. -type DocResolver interface { - Resolve(DID did.DID, metadata *ResolveMetaData) (*did.Document, *DocumentMetadata, error) -} - -// 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 -type DocCreator interface { - Create() (*did.Document, error) -} - -// DocWriter is the interface that groups al the DID Document write methods -type DocWriter interface { - // Write writes new DID Document. - // Returns ErrDIDAlreadyExists when DID already exists - // When a document already exists, the Update should be used instead - Write(DID did.Document) error -} - -// Update replaces the DID document identified by DID with the nextVersion -// To prevent updating state 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 -type DocUpdater interface { - Update(DID did.DID, hash []byte, nextVersion did.Document) (*did.Document, error) -} - -// 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 -} - -// 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 model.Hash `json:"originJwsHash"` - // Hash of DID document bytes. Is equal to payloadHash in network layer. - Hash string `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 []byte - // Allow DIDs which are deactivated - AllowDeactivated bool -} \ No newline at end of file diff --git a/vdr/vdr.go b/vdr/vdr.go index a010d40d8d..4dac6e2b20 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -1,6 +1,6 @@ /* - * Nuts registry - * 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 @@ -27,7 +27,8 @@ import ( "sync" "time" - "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-node/vdr/store" + "github.com/nuts-foundation/nuts-node/vdr/types" "github.com/sirupsen/logrus" "github.com/nuts-foundation/nuts-node/vdr/logging" @@ -57,6 +58,7 @@ import ( type Registry struct { Config Config //Db db.Db + store types.Store network networkPkg.NetworkClient crypto crypto.KeyStore OnChange func(registry *Registry) @@ -95,6 +97,7 @@ func NewRegistryInstance(config Config, cryptoClient crypto.KeyStore, networkCli crypto: cryptoClient, network: networkClient, _logger: logging.Log(), + store: store.NewMemoryStore(), } } @@ -142,27 +145,3 @@ func (r *Registry) Diagnostics() []core.DiagnosticResult { func (r *Registry) getEventsDir() string { return r.Config.Datadir + "/events" } - -func (r *Registry) Search(onlyOwn bool, tags []string) ([]did.Document, error) { - panic("implement me") -} - -func (r *Registry) Create() (*did.Document, error) { - panic("implement me") -} - -func (r *Registry) Get(DID did.DID) (*did.Document, *DocumentMetadata, error) { - panic("implement me") -} - -func (r *Registry) GetByTag(tag string) (*did.Document, *DocumentMetadata, error) { - panic("implement me") -} - -func (r *Registry) Update(DID did.DID, hash []byte, nextVersion did.Document) (*did.Document, error) { - panic("implement me") -} - -func (r *Registry) Tag(DID did.DID, tags []string) error { - panic("implement me") -} From c2d5b4b97c7bc54d60c874f739ff7a348f011053 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Tue, 19 Jan 2021 15:05:28 +0100 Subject: [PATCH 11/54] moved typers --- vdr/store/memory.go | 95 +++++++++++++++++++++++++++++++++ vdr/store/memory_test.go | 110 +++++++++++++++++++++++++++++++++++++++ vdr/types/common.go | 56 ++++++++++++++++++++ vdr/types/interface.go | 71 +++++++++++++++++++++++++ 4 files changed, 332 insertions(+) create mode 100644 vdr/store/memory.go create mode 100644 vdr/store/memory_test.go create mode 100644 vdr/types/common.go create mode 100644 vdr/types/interface.go diff --git a/vdr/store/memory.go b/vdr/store/memory.go new file mode 100644 index 0000000000..4c2508e50a --- /dev/null +++ b/vdr/store/memory.go @@ -0,0 +1,95 @@ +/* + * 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 ( + "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-network/pkg/model" + "github.com/nuts-foundation/nuts-node/vdr/types" +) + +// NewMemoryStore initializes a new in-memory store +func NewMemoryStore() types.Store { + return &memory{ + store: map[string]versionedEntryList{}, + } +} + +type versionedEntryList []memoryEntry + +type memory struct { + store map[string]versionedEntryList +} + +type memoryEntry struct { + document did.Document + metadata types.DocumentMetadata +} + +func (m *memory) Resolve(DID did.DID, metadata *types.ResolveMetaData) (*did.Document, *types.DocumentMetadata, error) { + entries, ok := m.store[DID.String()] + if !ok { + return nil, nil, types.ErrNotFound + } + + // filter on hash + if !hash.Equals(entry.metadata.Hash) { + return types.ErrUpdateOnOutdatedData + } + + // check deactivated which according to RFC006 is the case when no controllers and authenticationMethods exist + doc := entry.document + if len(doc.Controller) == 0 && len(doc.Authentication) == 0 { + return types.ErrDeactivated + } + + // hashes must match + if !hash.Equals(entry.metadata.Hash) { + return types.ErrUpdateOnOutdatedData + } + + return &entry.document, &entry.metadata, nil +} + +func (m *memory) Write(DIDDocument did.Document, metadata types.DocumentMetadata) error { + if _, ok := m.store[DIDDocument.ID.String()]; ok { + return types.ErrDIDAlreadyExists + } + + m.store[DIDDocument.ID.String()] = memoryEntry{ + document: DIDDocument, + metadata: metadata, + } + return nil +} + +func (m *memory) Update(DID did.DID, hash model.Hash, next did.Document, metadata types.DocumentMetadata) error { + rmd := &types.ResolveMetaData{ + Hash: &hash, + } + + // resolve will handle all the checks for us + if _, _, err := m.Resolve(DID, rmd); err != nil { + return err + } + + m.store[DID.ID] = memoryEntry{ + document: next, + metadata: metadata, + } + + return nil +} diff --git a/vdr/store/memory_test.go b/vdr/store/memory_test.go new file mode 100644 index 0000000000..8e2a8fb181 --- /dev/null +++ b/vdr/store/memory_test.go @@ -0,0 +1,110 @@ +/* + * 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" + + "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-network/pkg/model" + "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, + } + meta := types.DocumentMetadata{} + + _ = 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 when found", 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) + }) +} + +func TestMemory_Update(t *testing.T) { + store := memory{map[string]memoryEntry{}} + did1, _ := did.ParseDID("did:nuts:1") + doc := did.Document{ + ID: *did1, + Controller: []did.DID{*did1}, + } + h, _ := model.ParseHash("0000000000000000000000000000000000000000") + 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("returns error when hashes don't match", func(t *testing.T) { + h, _ := model.ParseHash("0000000000000000000000000000000000000001") + 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") + store.store[did1.String()] = memoryEntry{ + document: did.Document{ID: *did1}, + metadata: meta, + } + err := store.Update(*did1, h, doc, meta) + assert.Equal(t, types.ErrDeactivated, err) + }) +} diff --git a/vdr/types/common.go b/vdr/types/common.go new file mode 100644 index 0000000000..a1d2d87eb3 --- /dev/null +++ b/vdr/types/common.go @@ -0,0 +1,56 @@ +/* + * 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-network/pkg/model" +) + +var ErrUpdateOnOutdatedData = errors.New("could not update outdated document") +// ErrInvalidDID The DID supplied to the DID resolution function does not conform to valid syntax.0 +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 +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 model.Hash `json:"originJwsHash"` + // Hash of DID document bytes. Is equal to payloadHash in network layer. + Hash model.Hash `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 *model.Hash + // 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..0d1299951f --- /dev/null +++ b/vdr/types/interface.go @@ -0,0 +1,71 @@ +/* + * 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-network/pkg/model" +) + +// DocResolver is the interface that groups all the DID Document read methods +type DocResolver interface { + // Get returns the DID document using on the given DID or ErrNotFound if not found. + // If metadata is provided then the result is filtered or scoped on that meta data + // If metadata is not provided the latest version is returned + // If something goes wrong an error is returned. + Resolve(DID did.DID, metadata *ResolveMetaData) (*did.Document, *DocumentMetadata, error) +} + +// 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 +type DocCreator interface { + 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(DIDDocument 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 state 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 + // todo check the hash in VDR? + Update(DID did.DID, hash model.Hash, next did.Document, metadata DocumentMetadata) error +} + +// 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 +} From 33296298403361287c320c29ac816ca125b80820 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Tue, 19 Jan 2021 16:31:09 +0100 Subject: [PATCH 12/54] added versioning to memory store --- vdr/store/memory.go | 124 +++++++++++++++++++++++++++++------- vdr/store/memory_test.go | 132 ++++++++++++++++++++++++++++++++++++--- vdr/types/common.go | 2 +- 3 files changed, 228 insertions(+), 30 deletions(-) diff --git a/vdr/store/memory.go b/vdr/store/memory.go index 4c2508e50a..6784dbd8ce 100644 --- a/vdr/store/memory.go +++ b/vdr/store/memory.go @@ -28,7 +28,27 @@ func NewMemoryStore() types.Store { } } -type versionedEntryList []memoryEntry +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 @@ -37,6 +57,14 @@ type memory struct { type memoryEntry struct { document did.Document metadata types.DocumentMetadata + next *memoryEntry +} + +func (me memoryEntry) isDeactivated() bool { + if len(me.document.Controller) == 0 && len(me.document.Authentication) == 0 { + return true + } + return false } func (m *memory) Resolve(DID did.DID, metadata *types.ResolveMetaData) (*did.Document, *types.DocumentMetadata, error) { @@ -45,51 +73,103 @@ func (m *memory) Resolve(DID did.DID, metadata *types.ResolveMetaData) (*did.Doc return nil, nil, types.ErrNotFound } - // filter on hash - if !hash.Equals(entry.metadata.Hash) { - return types.ErrUpdateOnOutdatedData - } - - // check deactivated which according to RFC006 is the case when no controllers and authenticationMethods exist - doc := entry.document - if len(doc.Controller) == 0 && len(doc.Authentication) == 0 { - return types.ErrDeactivated + 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)) + } } - // hashes must match - if !hash.Equals(entry.metadata.Hash) { - return types.ErrUpdateOnOutdatedData + entry, err := entries.last() + if err != nil { + return nil, nil, err } return &entry.document, &entry.metadata, 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 + } +} + func (m *memory) Write(DIDDocument did.Document, metadata types.DocumentMetadata) error { if _, ok := m.store[DIDDocument.ID.String()]; ok { return types.ErrDIDAlreadyExists } - m.store[DIDDocument.ID.String()] = memoryEntry{ - document: DIDDocument, - metadata: metadata, + m.store[DIDDocument.ID.String()] = versionedEntryList{ + &memoryEntry{ + document: DIDDocument, + metadata: metadata, + }, } + return nil } +// Update also updates the Updated field of the latest version func (m *memory) Update(DID did.DID, hash model.Hash, next did.Document, metadata types.DocumentMetadata) error { - rmd := &types.ResolveMetaData{ - Hash: &hash, + entries, ok := m.store[DID.String()] + if !ok { + return types.ErrNotFound } - // resolve will handle all the checks for us - if _, _, err := m.Resolve(DID, rmd); err != nil { - return err + // latest version is to be updated + entry, _ := entries.last() + + if entry.isDeactivated() { + return types.ErrDeactivated + } + + // hashes must match + if !hash.Equals(entry.metadata.Hash) { + return types.ErrUpdateOnOutdatedData } - m.store[DID.ID] = memoryEntry{ + newEntry := &memoryEntry{ document: next, metadata: metadata, } + // update next in last document + entry.next = newEntry + + m.store[DID.String()] = append(entries, newEntry) + return nil } diff --git a/vdr/store/memory_test.go b/vdr/store/memory_test.go index 8e2a8fb181..7082fb843d 100644 --- a/vdr/store/memory_test.go +++ b/vdr/store/memory_test.go @@ -17,6 +17,7 @@ package store import ( "testing" + "time" "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/nuts-network/pkg/model" @@ -52,8 +53,15 @@ func TestMemory_Resolve(t *testing.T) { did1, _ := did.ParseDID("did:nuts:1") doc := did.Document{ ID: *did1, + Controller: []did.DID{*did1}, + } + + //upd := time.Now().Add(time.Hour * 24) + h, _ := model.ParseHash("0000000000000000000000000000000000000000") + meta := types.DocumentMetadata{ + Created: time.Now().Add(time.Hour * -24), + Hash: h, } - meta := types.DocumentMetadata{} _ = store.Write(doc, meta) @@ -63,7 +71,7 @@ func TestMemory_Resolve(t *testing.T) { assert.Equal(t, types.ErrNotFound, err) }) - t.Run("returns document when found", func(t *testing.T) { + t.Run("returns document without resolve metadata", func(t *testing.T) { d, m, err := store.Resolve(*did1, nil) if !assert.NoError(t, err) { return @@ -71,10 +79,96 @@ func TestMemory_Resolve(t *testing.T) { 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 := memory{map[string]memoryEntry{}} + store := NewMemoryStore() did1, _ := did.ParseDID("did:nuts:1") doc := did.Document{ ID: *did1, @@ -92,6 +186,26 @@ func TestMemory_Update(t *testing.T) { 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, _ := model.ParseHash("0000000000000000000000000000000000000001") err := store.Update(*did1, h, doc, meta) @@ -100,11 +214,15 @@ func TestMemory_Update(t *testing.T) { t.Run("returns error when DID Document is deactivated", func(t *testing.T) { did1, _ := did.ParseDID("did:nuts:2") - store.store[did1.String()] = memoryEntry{ - document: did.Document{ID: *did1}, - metadata: meta, + doc := did.Document{ + ID: *did1, } - err := store.Update(*did1, h, doc, meta) + 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/types/common.go b/vdr/types/common.go index a1d2d87eb3..712e5d7ab9 100644 --- a/vdr/types/common.go +++ b/vdr/types/common.go @@ -36,7 +36,7 @@ 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"` + 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. From e5db5a4c1dc8809c61e81eb331688d2f7b9d8ae7 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Tue, 19 Jan 2021 16:33:33 +0100 Subject: [PATCH 13/54] added mutex to memory store --- vdr/store/memory.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/vdr/store/memory.go b/vdr/store/memory.go index 6784dbd8ce..12e0d6ef6a 100644 --- a/vdr/store/memory.go +++ b/vdr/store/memory.go @@ -16,6 +16,8 @@ package store import ( + "sync" + "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/nuts-network/pkg/model" "github.com/nuts-foundation/nuts-node/vdr/types" @@ -25,6 +27,7 @@ import ( func NewMemoryStore() types.Store { return &memory{ store: map[string]versionedEntryList{}, + mutex: sync.Mutex{}, } } @@ -52,6 +55,7 @@ func (list versionedEntryList) last() (*memoryEntry, error) { type memory struct { store map[string]versionedEntryList + mutex sync.Mutex } type memoryEntry struct { @@ -68,6 +72,9 @@ func (me memoryEntry) isDeactivated() bool { } func (m *memory) Resolve(DID did.DID, metadata *types.ResolveMetaData) (*did.Document, *types.DocumentMetadata, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + entries, ok := m.store[DID.String()] if !ok { return nil, nil, types.ErrNotFound @@ -128,6 +135,9 @@ func timeSelectionFilter(metadata types.ResolveMetaData) filterFunc { } func (m *memory) Write(DIDDocument did.Document, metadata types.DocumentMetadata) error { + m.mutex.Lock() + defer m.mutex.Unlock() + if _, ok := m.store[DIDDocument.ID.String()]; ok { return types.ErrDIDAlreadyExists } @@ -144,6 +154,9 @@ func (m *memory) Write(DIDDocument did.Document, metadata types.DocumentMetadata // Update also updates the Updated field of the latest version func (m *memory) Update(DID did.DID, hash model.Hash, next did.Document, metadata types.DocumentMetadata) error { + m.mutex.Lock() + defer m.mutex.Unlock() + entries, ok := m.store[DID.String()] if !ok { return types.ErrNotFound From 30e43098e77f4607f0538ecd2859d2c1483a0ea9 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Tue, 19 Jan 2021 16:38:45 +0100 Subject: [PATCH 14/54] added some comments --- vdr/store/memory.go | 4 +++- vdr/types/interface.go | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/vdr/store/memory.go b/vdr/store/memory.go index 12e0d6ef6a..df5636a1fd 100644 --- a/vdr/store/memory.go +++ b/vdr/store/memory.go @@ -24,6 +24,7 @@ import ( ) // 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{}, @@ -152,7 +153,8 @@ func (m *memory) Write(DIDDocument did.Document, metadata types.DocumentMetadata return nil } -// Update also updates the Updated field of the latest version +// Update 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(DID did.DID, hash model.Hash, next did.Document, metadata types.DocumentMetadata) error { m.mutex.Lock() defer m.mutex.Unlock() diff --git a/vdr/types/interface.go b/vdr/types/interface.go index 0d1299951f..ba3b70b644 100644 --- a/vdr/types/interface.go +++ b/vdr/types/interface.go @@ -52,7 +52,6 @@ type DocUpdater interface { // 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 - // todo check the hash in VDR? Update(DID did.DID, hash model.Hash, next did.Document, metadata DocumentMetadata) error } From 1c7d8fd9e0daae0918b731d2f4fdde1e589ba51c Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Tue, 19 Jan 2021 17:05:45 +0100 Subject: [PATCH 15/54] Allow kidNamingFunc to return error --- crypto/crypto.go | 7 +++++-- crypto/interface.go | 2 +- crypto/test.go | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crypto/crypto.go b/crypto/crypto.go index 580843174c..e2072733db 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -1,6 +1,6 @@ /* * Nuts crypto - * Copyright (C) 2019. Nuts community + * Copyright (C) 2020. 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 @@ -141,7 +141,10 @@ func (client *Crypto) New(namingFunc KidNamingFunc) (crypto.PublicKey, string, e 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/interface.go b/crypto/interface.go index 993d152ba6..92de65b441 100644 --- a/crypto/interface.go +++ b/crypto/interface.go @@ -23,7 +23,7 @@ import ( ) // KidNamingFunc is a function passed to New() which generates the kid for the pub/priv key -type KidNamingFunc func(key crypto.PublicKey) string +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 { diff --git a/crypto/test.go b/crypto/test.go index 4a9ccbe7d6..3cafac021a 100644 --- a/crypto/test.go +++ b/crypto/test.go @@ -30,7 +30,7 @@ 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 + return func(key crypto.PublicKey) (string, error) { + return name, nil } } From da2372febc9e8a04a8066c90b15ade007302c35b Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Tue, 19 Jan 2021 17:06:00 +0100 Subject: [PATCH 16/54] Add crypto KeyCreator interface --- crypto/interface.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crypto/interface.go b/crypto/interface.go index 92de65b441..50c837bc87 100644 --- a/crypto/interface.go +++ b/crypto/interface.go @@ -25,11 +25,16 @@ import ( // 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 { +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) +} + +// KeyStore defines the functions than 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) From e0c4e153e8657d4021fb4b098c402878880e7189 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Tue, 19 Jan 2021 17:07:43 +0100 Subject: [PATCH 17/54] Add docCreator which creates did document Simple doc creator which creates a key and creates a simple did document. Needs more tests etc. --- go.mod | 1 + go.sum | 9 +--- vdr/doc-creator.go | 97 +++++++++++++++++++++++++++++++++++++++++ vdr/doc-creator_test.go | 41 +++++++++++++++++ vdr/vdr.go | 15 ++++++- 5 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 vdr/doc-creator.go create mode 100644 vdr/doc-creator_test.go diff --git a/go.mod b/go.mod index c2e652ca06..528d66d483 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( 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 a5cc6f53a3..36c4ba329b 100644 --- a/go.sum +++ b/go.sum @@ -77,8 +77,6 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc 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/cyberphone/json-canonicalization v0.0.0-20200417180520-cd6247b5f11e h1:v7sORZGlE3dVwkSPHuG4RNsxT1bOyHXw09A2BJ8dxvQ= -github.com/cyberphone/json-canonicalization v0.0.0-20200417180520-cd6247b5f11e/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= 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= @@ -180,8 +178,6 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI 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/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0= -github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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= @@ -284,7 +280,6 @@ github.com/labstack/echo/v4 v4.1.17 h1:PQIBaRplyRy3OjwILGkPg89JRtH2x5bssi59G2EL3 github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/leodido/go-urn v1.2.1-0.20201207182545-66fa6ee96763/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lestrrat-go/backoff/v2 v2.0.3 h1:2ABaTa5ifB1L90aoRMjaPa97p0WzzVe93Vggv8oZftw= github.com/lestrrat-go/backoff/v2 v2.0.3/go.mod h1:mU93bMXuG27/Y5erI5E9weqavpTX5qiVFZI4uXAX0xk= github.com/lestrrat-go/httpcc v0.0.0-20210101035852-e7e8fea419e3 h1:e52qvXxpJPV/Kb2ovtuYgcRFjNmf9ntcn8BPIbpRM4k= @@ -363,8 +358,6 @@ github.com/nuts-foundation/nuts-go-test v0.16.0 h1:jfbh2z44FAsRSPXquTZWviOlB9xb9 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/nuts-foundation/nuts-registry v0.16.0 h1:pcZVJExFoDl6axgG7WiIVmGhl8e1J1nP0VyUpZSucY4= -github.com/nuts-foundation/nuts-registry v0.16.0/go.mod h1:thE+6wah6xy6tMe/RRRY69ZWR4OiEVTD4MAVnLhdJbs= 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= @@ -428,6 +421,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD 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= diff --git a/vdr/doc-creator.go b/vdr/doc-creator.go new file mode 100644 index 0000000000..adea231fc4 --- /dev/null +++ b/vdr/doc-creator.go @@ -0,0 +1,97 @@ +package vdr + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/sha256" + "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" +) + +const NutsDIDMethodName = "nuts" + +type DocCreator struct { + keyCreator nutsCrypto.KeyCreator +} + +func didKidNamingFunc(pKey crypto.PublicKey) (string, error) { + ecPKey := pKey.(*ecdsa.PublicKey) + pkBytes := elliptic.Marshal(ecPKey.Curve, ecPKey.X, ecPKey.Y) + pkHash := sha256.Sum256(pkBytes) + + jwKey, err := jwk.New(pKey) + if err != nil { + return "", err + } + err = jwk.AssignKeyID(jwKey) + if err != nil { + return "", err + } + + idString := base58.Encode(pkHash[:], base58.BitcoinAlphabet) + kid := &did.DID{} + kid.ID = idString + kid.Method = NutsDIDMethodName + kid.Fragment = jwKey.KeyID() + + return kid.String(), nil +} + +//BuildDID +func (n DocCreator) Create() (*did.Document, error) { + key, kidIDStr, err := n.keyCreator.New(didKidNamingFunc) + if err != nil { + return nil, fmt.Errorf("unable to build did: %w", err) + } + didID, err := did.ParseDID(kidIDStr) + didID.Fragment = "" + + publicKeyJWK, err := jwk.New(key) + if err != nil { + return nil, err + } + err = publicKeyJWK.Set(jwk.KeyIDKey, kidIDStr) + if err != nil { + return nil, err + } + + jwk.AssignKeyID(publicKeyJWK) + if err != nil { + return nil, err + } + + verificationMethod, err := jwkToVerificationMethod(publicKeyJWK) + + doc := &did.Document{ + Context: []did.URI{did.DIDContextV1URI()}, + ID: *didID, + VerificationMethod: []did.VerificationMethod{*verificationMethod}, + Authentication: []did.VerificationRelationship{{VerificationMethod: verificationMethod}}, + } + return doc, nil +} + +func jwkToVerificationMethod(key jwk.Key) (*did.VerificationMethod, error) { + publicKeyAsJWKAsMap, err := key.AsMap(context.Background()) + if err != nil { + return nil, err + } + kid, err := url.Parse(key.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..1475a1cd85 --- /dev/null +++ b/vdr/doc-creator_test.go @@ -0,0 +1,41 @@ +package vdr + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + + nutsCrypto "github.com/nuts-foundation/nuts-node/crypto" +) + +type mockKeyCreator struct { + called bool +} + +func (m *mockKeyCreator) New(namingFunc nutsCrypto.KidNamingFunc) (crypto.PublicKey, string, error) { + m.called = true + keyPair, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, "", err + } + kid, _ := namingFunc(keyPair.Public()) + return keyPair.Public(), kid, nil +} + +func TestDocCreator_Create(t *testing.T) { + kc := &mockKeyCreator{} + sut := DocCreator{keyCreator: kc} + t.Run("ok", func(t *testing.T) { + doc, err := sut.Create() + assert.NoError(t, err) + assert.NotNil(t, doc) + assert.True(t, kc.called) + didDocJson, _ := json.Marshal(doc) + t.Log(string(didDocJson)) + }) +} diff --git a/vdr/vdr.go b/vdr/vdr.go index 4dac6e2b20..285a71317d 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -27,9 +27,11 @@ import ( "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/sirupsen/logrus" "github.com/nuts-foundation/nuts-node/vdr/logging" @@ -68,6 +70,10 @@ type Registry struct { closers []chan struct{} } +type VDR struct { + didDocCreator DocCreator +} + var instance *Registry var oneRegistry sync.Once @@ -145,3 +151,10 @@ func (r *Registry) Diagnostics() []core.DiagnosticResult { func (r *Registry) getEventsDir() string { return r.Config.Datadir + "/events" } + +func (v VDR) Create() (*did.Document, error) { + return v.didDocCreator.Create() +} + + + From d757b21d854272807b46e2bad72a218b60c7dd81 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Wed, 20 Jan 2021 09:30:51 +0100 Subject: [PATCH 18/54] Code cleanup of did doc create --- vdr/doc-creator.go | 58 ++++++++++++++++++++++++----------------- vdr/doc-creator_test.go | 46 ++++++++++++++++++++------------ 2 files changed, 63 insertions(+), 41 deletions(-) diff --git a/vdr/doc-creator.go b/vdr/doc-creator.go index adea231fc4..e4c7431d8b 100644 --- a/vdr/doc-creator.go +++ b/vdr/doc-creator.go @@ -6,6 +6,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/sha256" + "errors" "fmt" "net/url" @@ -24,10 +25,19 @@ type DocCreator struct { } func didKidNamingFunc(pKey crypto.PublicKey) (string, error) { - ecPKey := pKey.(*ecdsa.PublicKey) + ecPKey, ok := pKey.(*ecdsa.PublicKey) + if !ok { + return "", errors.New("could not generate kid: invalid key type") + } + + // 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 @@ -37,39 +47,29 @@ func didKidNamingFunc(pKey crypto.PublicKey) (string, error) { return "", err } - idString := base58.Encode(pkHash[:], base58.BitcoinAlphabet) + // assemble kid := &did.DID{} - kid.ID = idString kid.Method = NutsDIDMethodName + kid.ID = idString kid.Fragment = jwKey.KeyID() return kid.String(), nil } -//BuildDID +// Create creates a 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 DocCreator) Create() (*did.Document, error) { - key, kidIDStr, err := n.keyCreator.New(didKidNamingFunc) + // 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) } - didID, err := did.ParseDID(kidIDStr) - didID.Fragment = "" - publicKeyJWK, err := jwk.New(key) - if err != nil { - return nil, err - } - err = publicKeyJWK.Set(jwk.KeyIDKey, kidIDStr) - if err != nil { - return nil, err - } - - jwk.AssignKeyID(publicKeyJWK) - if err != nil { - return nil, err - } + // The Document DID can be generated from the keyID without the fragment: + didID, err := did.ParseDID(keyID) + didID.Fragment = "" - verificationMethod, err := jwkToVerificationMethod(publicKeyJWK) + verificationMethod, err := keyToVerificationMethod(key, keyID) doc := &did.Document{ Context: []did.URI{did.DIDContextV1URI()}, @@ -80,12 +80,22 @@ func (n DocCreator) Create() (*did.Document, error) { return doc, nil } -func jwkToVerificationMethod(key jwk.Key) (*did.VerificationMethod, error) { - publicKeyAsJWKAsMap, err := key.AsMap(context.Background()) +// 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(key.KeyID()) + kid, err := url.Parse(publicKeyJWK.KeyID()) if err != nil { return nil, err } diff --git a/vdr/doc-creator_test.go b/vdr/doc-creator_test.go index 1475a1cd85..50d6aac845 100644 --- a/vdr/doc-creator_test.go +++ b/vdr/doc-creator_test.go @@ -2,40 +2,52 @@ package vdr import ( "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "encoding/json" "testing" + "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 { - called bool + // jwkStr hold the predefined key in a json web key string + jwkStr string } +// 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) { - m.called = true - keyPair, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + keySet, err := jwk.ParseString(m.jwkStr) if err != nil { return nil, "", err } - kid, _ := namingFunc(keyPair.Public()) - return keyPair.Public(), kid, nil + key := keySet.Keys[0] + + var rawKey crypto.PublicKey + if key.Raw(&rawKey) != nil { + return nil, "", err + } + kid, err := namingFunc(rawKey) + if err != nil { + return nil, "", err + } + return rawKey, kid, nil } func TestDocCreator_Create(t *testing.T) { - kc := &mockKeyCreator{} - sut := DocCreator{keyCreator: kc} t.Run("ok", func(t *testing.T) { - doc, err := sut.Create() - assert.NoError(t, err) - assert.NotNil(t, doc) - assert.True(t, kc.called) - didDocJson, _ := json.Marshal(doc) - t.Log(string(didDocJson)) + kc := &mockKeyCreator{ + `{"crv":"P-256","kid":"did:nuts:ARRW2e42qyVjQZiACk4Up3mzpshZdJBDBPWsuFQPcDiS#J9O6wvqtYOVwjc8JtZ4aodRdbPv_IKAjLkEq9uHlDdE","kty":"EC","x":"Qn6xbZtOYFoLO2qMEAczcau9uGGWwa1bT+7JmAVLtg4=","y":"d20dD0qlT+d1djVpAfrfsAfKOUxKwKkn1zqFSIuJ398="},"type":"JsonWebKey2020"}`, + } + sut := DocCreator{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.VerificationMethod, 1) + assert.Equal(t, "did:nuts:ARRW2e42qyVjQZiACk4Up3mzpshZdJBDBPWsuFQPcDiS#J9O6wvqtYOVwjc8JtZ4aodRdbPv_IKAjLkEq9uHlDdE", doc.VerificationMethod[0].ID.String()) + }) }) } From 0d59ea586428151c69a70e115272575ab0ccb310 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Wed, 20 Jan 2021 09:31:18 +0100 Subject: [PATCH 19/54] Registry implements Create --- vdr/vdr.go | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/vdr/vdr.go b/vdr/vdr.go index 285a71317d..28382ec4e5 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -24,10 +24,12 @@ package vdr import ( + "fmt" "sync" "time" "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-network/pkg/model" "github.com/sirupsen/logrus" "github.com/nuts-foundation/nuts-node/vdr/store" @@ -46,7 +48,6 @@ import ( "github.com/nuts-foundation/nuts-node/crypto" ) - //type StoreWrapper struct { // networkClient networkPkg.NetworkClient // store DIDStore @@ -58,9 +59,9 @@ import ( // Registry holds the config and Db reference type Registry struct { - Config Config + Config Config //Db db.Db - store types.Store + store types.Store network networkPkg.NetworkClient crypto crypto.KeyStore OnChange func(registry *Registry) @@ -68,10 +69,10 @@ type Registry struct { configOnce sync.Once _logger *logrus.Entry closers []chan struct{} + didDocCreator DocCreator } type VDR struct { - didDocCreator DocCreator } var instance *Registry @@ -103,7 +104,7 @@ func NewRegistryInstance(config Config, cryptoClient crypto.KeyStore, networkCli crypto: cryptoClient, network: networkClient, _logger: logging.Log(), - store: store.NewMemoryStore(), + store: store.NewMemoryStore(), } } @@ -152,9 +153,28 @@ func (r *Registry) getEventsDir() string { return r.Config.Datadir + "/events" } -func (v VDR) Create() (*did.Document, error) { - return v.didDocCreator.Create() -} +func (r Registry) 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 +} +func (r Registry) Resolve(DID did.DID, metadata *types.ResolveMetaData) (*did.Document, *types.DocumentMetadata, error) { + panic("implement me") +} +func (r Registry) Update(DID did.DID, hash model.Hash, next did.Document, metadata types.DocumentMetadata) error { + panic("implement me") +} From fedc8a711550ff014b4551e8bb6c3c429830a77a Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Wed, 20 Jan 2021 09:47:21 +0100 Subject: [PATCH 20/54] Add didDocCreator to Registry --- vdr/doc-creator.go | 9 ++++++--- vdr/doc-creator_test.go | 2 +- vdr/vdr.go | 13 +++++++------ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/vdr/doc-creator.go b/vdr/doc-creator.go index e4c7431d8b..57c5fbbeb8 100644 --- a/vdr/doc-creator.go +++ b/vdr/doc-creator.go @@ -20,7 +20,9 @@ import ( const NutsDIDMethodName = "nuts" -type DocCreator struct { +// 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 } @@ -31,6 +33,7 @@ func didKidNamingFunc(pKey crypto.PublicKey) (string, error) { } // according to RFC006: + // -------------------- // generate idString pkBytes := elliptic.Marshal(ecPKey.Curve, ecPKey.X, ecPKey.Y) @@ -56,9 +59,9 @@ func didKidNamingFunc(pKey crypto.PublicKey) (string, error) { return kid.String(), nil } -// Create creates a DID Document with a valid DID id based on a freshly generated keypair. +// 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 DocCreator) Create() (*did.Document, error) { +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 { diff --git a/vdr/doc-creator_test.go b/vdr/doc-creator_test.go index 50d6aac845..1705d47e55 100644 --- a/vdr/doc-creator_test.go +++ b/vdr/doc-creator_test.go @@ -40,7 +40,7 @@ func TestDocCreator_Create(t *testing.T) { kc := &mockKeyCreator{ `{"crv":"P-256","kid":"did:nuts:ARRW2e42qyVjQZiACk4Up3mzpshZdJBDBPWsuFQPcDiS#J9O6wvqtYOVwjc8JtZ4aodRdbPv_IKAjLkEq9uHlDdE","kty":"EC","x":"Qn6xbZtOYFoLO2qMEAczcau9uGGWwa1bT+7JmAVLtg4=","y":"d20dD0qlT+d1djVpAfrfsAfKOUxKwKkn1zqFSIuJ398="},"type":"JsonWebKey2020"}`, } - sut := DocCreator{keyCreator: kc} + sut := NutsDocCreator{keyCreator: kc} t.Run("ok", func(t *testing.T) { doc, err := sut.Create() assert.NoError(t, err) diff --git a/vdr/vdr.go b/vdr/vdr.go index 28382ec4e5..e1961011e4 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -69,7 +69,7 @@ type Registry struct { configOnce sync.Once _logger *logrus.Entry closers []chan struct{} - didDocCreator DocCreator + didDocCreator types.DocCreator } type VDR struct { @@ -100,11 +100,12 @@ func RegistryInstance() *Registry { func NewRegistryInstance(config Config, cryptoClient crypto.KeyStore, networkClient pkg.NetworkClient) *Registry { return &Registry{ - Config: config, - crypto: cryptoClient, - network: networkClient, - _logger: logging.Log(), - store: store.NewMemoryStore(), + Config: config, + crypto: cryptoClient, + network: networkClient, + _logger: logging.Log(), + store: store.NewMemoryStore(), + didDocCreator: NutsDocCreator{keyCreator: cryptoClient}, } } From d0d87b56c03b928c088630d5a953892217567811 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Wed, 20 Jan 2021 09:58:37 +0100 Subject: [PATCH 21/54] Fix crypto mock Added generator to the make file --- crypto/{ => mock}/mock.go | 52 ++++++++++++++++++++++++++++++++++----- makefile | 5 ++++ 2 files changed, 51 insertions(+), 6 deletions(-) rename crypto/{ => mock}/mock.go (67%) diff --git a/crypto/mock.go b/crypto/mock/mock.go similarity index 67% rename from crypto/mock.go rename to crypto/mock/mock.go index de9d7f8650..182f224474 100644 --- a/crypto/mock.go +++ b/crypto/mock/mock.go @@ -2,14 +2,54 @@ // Source: crypto/interface.go // Package mock is a generated GoMock package. -package crypto +package mock 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 crypto0.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 @@ -34,7 +74,7 @@ func (m *MockKeyStore) EXPECT() *MockKeyStoreMockRecorder { } // New mocks base method -func (m *MockKeyStore) New(namingFunc KidNamingFunc) (crypto.PublicKey, string, error) { +func (m *MockKeyStore) New(namingFunc crypto0.KidNamingFunc) (crypto.PublicKey, string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "New", namingFunc) ret0, _ := ret[0].(crypto.PublicKey) @@ -95,15 +135,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/makefile b/makefile index 9dd8ec2108..9a2b6830ca 100644 --- a/makefile +++ b/makefile @@ -1,3 +1,8 @@ +.PHONY: test run-generators update-docs + +run-generators: + mockgen -destination=crypto/mock/mock.go -package=mock -source=crypto/interface.go + test: go test ./... From 649817bfa3c3845e2b708116c47ba9b80ae02141 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Wed, 20 Jan 2021 10:07:06 +0100 Subject: [PATCH 22/54] Fix paths in readme and add oapi-gen to makefile --- docs/pages/development.rst | 5 ++--- makefile | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/pages/development.rst b/docs/pages/development.rst index e69164122a..5ff11dcf48 100644 --- a/docs/pages/development.rst +++ b/docs/pages/development.rst @@ -29,7 +29,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 > crypto/api/v1/generated.go - oapi-codegen -generate types,server,client -package v1 docs/_static/did/v1.yaml > did/api/v1/generated.go + oapi-codegen -generate types,server,client -package v1 docs/_static/did/v1.yaml > vdr/api/v1/generated.go Generating Mocks **************** @@ -38,8 +38,7 @@ These mocks are used by other modules .. code-block:: shell - mockgen -destination=crypto/mock.go -package=crypto -source=crypto/interface.go KeyStore - + mockgen -destination=crypto/mock/mock.go -package=mock -source=crypto/interface.go KeyStore README ****** diff --git a/makefile b/makefile index 9a2b6830ca..c20fc9aa04 100644 --- a/makefile +++ b/makefile @@ -2,6 +2,8 @@ run-generators: mockgen -destination=crypto/mock/mock.go -package=mock -source=crypto/interface.go + oapi-codegen -generate types,server,client -package v1 docs/_static/crypto/v1.yaml > crypto/api/v1/generated.go + oapi-codegen -generate types,server,client -package v1 docs/_static/did/v1.yaml > vdr/api/v1/generated.go test: go test ./... From 39e836b929be7763f27be428797206f8e542569f Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Wed, 20 Jan 2021 10:13:03 +0100 Subject: [PATCH 23/54] Fix code generation + update generated readme --- README.rst | 57 +++++++++++++++++++++++++++++++++----- README_template.rst | 2 +- crypto/api/v1/generated.go | 1 + makefile | 16 ++++++++--- 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index 0c40cd645c..f6920ff9b4 100644 --- a/README.rst +++ b/README.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/69f77bd34f3ac253cae0/maintainability +.. image:: https://api.codeclimate.com/v1/badges/040468237c838c03ff7d/maintainability :target: https://codeclimate.com/github/nuts-foundation/nuts-node/maintainability :alt: Maintainability @@ -42,7 +42,8 @@ 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 + oapi-codegen -generate types,server,client -package v1 docs/_static/crypto/v1.yaml > crypto/api/v1/generated.go + oapi-codegen -generate types,server,client -package v1 docs/_static/did/v1.yaml > vdr/api/v1/generated.go Generating Mocks **************** @@ -51,8 +52,7 @@ These mocks are used by other modules .. code-block:: shell - mockgen -destination=mock/mock_example.go -package=mock -source=example.go - + mockgen -destination=crypto/mock/mock.go -package=mock -source=crypto/interface.go KeyStore README ****** @@ -74,11 +74,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..adbff17ee3 100644 --- a/README_template.rst +++ b/README_template.rst @@ -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/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/makefile b/makefile index c20fc9aa04..9ddd273117 100644 --- a/makefile +++ b/makefile @@ -1,13 +1,21 @@ .PHONY: test run-generators update-docs -run-generators: +run-generators: gen-readme gen-mocks gen-api + +gen-readme: + ./generate_readme.sh + +gen-mocks: mockgen -destination=crypto/mock/mock.go -package=mock -source=crypto/interface.go + +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 -package v1 docs/_static/did/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 From f9bf79aac37e29f6e42dbae81b766c567da2b63c Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Wed, 20 Jan 2021 10:17:49 +0100 Subject: [PATCH 24/54] Naively connected the VDR to the store. --- vdr/vdr.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vdr/vdr.go b/vdr/vdr.go index e1961011e4..3738e98306 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -172,10 +172,10 @@ func (r Registry) Create() (*did.Document, error) { return doc, nil } -func (r Registry) Resolve(DID did.DID, metadata *types.ResolveMetaData) (*did.Document, *types.DocumentMetadata, error) { - panic("implement me") +func (r Registry) Resolve(dID did.DID, metadata *types.ResolveMetaData) (*did.Document, *types.DocumentMetadata, error) { + return r.store.Resolve(dID, metadata) } -func (r Registry) Update(DID did.DID, hash model.Hash, next did.Document, metadata types.DocumentMetadata) error { - panic("implement me") +func (r Registry) Update(dID did.DID, hash model.Hash, next did.Document, metadata types.DocumentMetadata) error { + return r.store.Update(dID, hash, next, metadata) } From 40eb43990556aac0ac4b5a680357574043737326 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Wed, 20 Jan 2021 10:28:51 +0100 Subject: [PATCH 25/54] Add DocDeactivator interface --- vdr/types/interface.go | 13 ++++++++++++- vdr/vdr.go | 8 +++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/vdr/types/interface.go b/vdr/types/interface.go index ba3b70b644..377ea352c7 100644 --- a/vdr/types/interface.go +++ b/vdr/types/interface.go @@ -48,13 +48,23 @@ type DocWriter interface { // 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 state data a hash of the current version should be provided. + // 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(DID did.DID, hash model.Hash, 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(DID did.DID, hash model.Hash) +} + // Store is the interface that groups all low level VDR DID storage operations. type Store interface { DocResolver @@ -67,4 +77,5 @@ type VDR interface { DocResolver DocCreator DocUpdater + DocDeactivator } diff --git a/vdr/vdr.go b/vdr/vdr.go index 3738e98306..280817348a 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -72,9 +72,6 @@ type Registry struct { didDocCreator types.DocCreator } -type VDR struct { -} - var instance *Registry var oneRegistry sync.Once @@ -179,3 +176,8 @@ func (r Registry) Resolve(dID did.DID, metadata *types.ResolveMetaData) (*did.Do func (r Registry) Update(dID did.DID, hash model.Hash, next did.Document, metadata types.DocumentMetadata) error { return r.store.Update(dID, hash, next, metadata) } + +func (r *Registry) Deactivate(DID did.DID, hash model.Hash) { + panic("implement me") +} + From f495f790a5f0e9f3bbc321a8a0fdfb18fd3877c3 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Wed, 20 Jan 2021 15:31:21 +0100 Subject: [PATCH 26/54] added server and client implementation for VDR API --- crypto/api/v1/api_test.go | 2 +- crypto/engine/engine_test.go | 1 + crypto/jwx_test.go | 2 +- crypto/{mock => }/mock.go | 9 +- docs/_static/did/v1.yaml | 205 ---------------- docs/_static/vdr/v1.yaml | 113 +++++++++ docs/pages/development.rst | 7 +- makefile | 5 +- vdr/api/v1/api.go | 78 ++++-- vdr/api/v1/api_test.go | 249 +++++++++++++++++++ vdr/api/v1/client.go | 68 +++--- vdr/api/v1/client_test.go | 142 +++++++++++ vdr/api/v1/generated.go | 453 ++++------------------------------- vdr/api/v1/types.go | 34 +++ vdr/config.go | 1 - vdr/engine/engine.go | 4 +- vdr/store/memory.go | 10 +- vdr/store/memory_test.go | 10 +- vdr/types/common.go | 7 +- vdr/types/mock.go | 345 ++++++++++++++++++++++++++ vdr/vdr.go | 1 - 21 files changed, 1064 insertions(+), 682 deletions(-) rename crypto/{mock => }/mock.go (93%) delete mode 100644 docs/_static/did/v1.yaml create mode 100644 docs/_static/vdr/v1.yaml create mode 100644 vdr/api/v1/api_test.go create mode 100644 vdr/api/v1/client_test.go create mode 100644 vdr/api/v1/types.go create mode 100644 vdr/types/mock.go diff --git a/crypto/api/v1/api_test.go b/crypto/api/v1/api_test.go index c8fe0ab8e9..7ae5a66459 100644 --- a/crypto/api/v1/api_test.go +++ b/crypto/api/v1/api_test.go @@ -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/engine/engine_test.go b/crypto/engine/engine_test.go index f27db3e97f..7ebbb66c9f 100644 --- a/crypto/engine/engine_test.go +++ b/crypto/engine/engine_test.go @@ -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/jwx_test.go b/crypto/jwx_test.go index b511801ed3..f4ab7f9cc5 100644 --- a/crypto/jwx_test.go +++ b/crypto/jwx_test.go @@ -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/mock/mock.go b/crypto/mock.go similarity index 93% rename from crypto/mock/mock.go rename to crypto/mock.go index 182f224474..2dd1e3fa84 100644 --- a/crypto/mock/mock.go +++ b/crypto/mock.go @@ -1,13 +1,12 @@ // 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" ) @@ -35,7 +34,7 @@ func (m *MockKeyCreator) EXPECT() *MockKeyCreatorMockRecorder { } // New mocks base method -func (m *MockKeyCreator) New(namingFunc crypto0.KidNamingFunc) (crypto.PublicKey, string, error) { +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) @@ -74,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) diff --git a/docs/_static/did/v1.yaml b/docs/_static/did/v1.yaml deleted file mode 100644 index a2671fd61e..0000000000 --- a/docs/_static/did/v1.yaml +++ /dev/null @@ -1,205 +0,0 @@ -openapi: "3.0.0" -info: - title: Nuts registry API spec - description: API specification for RPC services available at the nuts-registry - version: 1.0.0 - license: - name: GPLv3 -paths: - /internal/registry/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 - get: - summary: "Searches for Nuts DIDs" - operationId: searchDID - tags: - - DID - parameters: - - name: tags - in: query - description: "URL encoded DID or tag. When given a tag it must resolve to exactly one DID." - required: true - example: - - "did:nuts:1234" - - "tag:client-987731" - schema: - type: string - responses: - "200": - description: "Search successful, response contains the DIDs" - content: - application/json: - schema: - type: array - items: - type: string - "500": - description: "An error occurred while processing the request." - content: - text/plain: - schema: - type: string - /internal/registry/v1/did/{didOrTag}: - parameters: - - name: didOrTag - in: path - description: "URL encoded DID or tag. When given a tag it must resolve to exactly one DID." - required: true - example: - - "did:nuts:1234" - - "tag:client-987731" - 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: - schema: - $ref: "#/components/schemas/DIDResolutionResult" - "404": - description: "DID couldn't 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: - properties: - document: - $ref: "#/components/schemas/DIDDocument" - currentHash: - description: "SHA-256 hash of the last version of the DID Document" - type: string - 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." - content: - text/plain: - schema: - type: string - "404": - description: "DID couldn't be found." - content: - text/plain: - schema: - type: string - "500": - description: "An error occurred while processing the request." - content: - text/plain: - schema: - type: string - /internal/registry/v1/did/{didOrTag}/tag: - parameters: - - name: didOrTag - in: path - description: "URL encoded DID or tag. When given a tag it must resolve to exactly one DID." - required: true - example: - - "did:nuts:1234" - - "tag:client-987731" - schema: - type: string - post: - summary: "Replaces the tags of the DID Document." - operationId: updateDIDTags - requestBody: - description: "The new set of tags for the DID Document." - required: true - content: - application/json: - schema: - type: array - items: - type: string - responses: - "204": - description: "DID Document tags have been updated." - "404": - description: "DID couldn't 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: The actual DID Document in JSON representation. - DIDDocumentMetadata: - properties: - created: - description: "Date/time at which the document was originally created." - type: string - format: date-time - updated: - description: "Date/time at which the document (or this version) was updated." - type: string - format: date-time - version: - description: "Semantic version of the DID document." - type: integer - originJwsHash: - description: "Hash (SHA-256, hex-encoded) of the JWS envelope of the first version of the DID document." - type: string - hash: - description: "Hash (SHA-256, hex-encoded) of DID document bytes. Is equal to payloadHash in network layer." - type: string - DIDResolutionResult: - properties: - document: - $ref: "#/components/schemas/DIDDocument" - documentMetadata: - $ref: '#/components/schemas/DIDDocumentMetadata' - resolutionMetadata: - type: object - description: Metadata collected during DID Document (a.k.a. DID Resolution Metadata). \ No newline at end of file diff --git a/docs/_static/vdr/v1.yaml b/docs/_static/vdr/v1.yaml new file mode 100644 index 0000000000..fd2bdc4afe --- /dev/null +++ b/docs/_static/vdr/v1.yaml @@ -0,0 +1,113 @@ +openapi: "3.0.0" +info: + title: Nuts verifiable dataa registry API spec + description: API specification for RPC services available at the nuts-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: + schema: + type: object + description: A DIDResolutionResult is returned. + "400": + description: "given DID is incorrect." + content: + text/plain: + schema: + type: string + "404": + description: "DID couldn't 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: + type: object + description: JSON object with document, documentMetadata and currentHash + 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." + content: + text/plain: + schema: + type: string + "404": + description: "DID couldn't 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: The actual DID Document in JSON representation. + DIDResolutionResult: + type: object diff --git a/docs/pages/development.rst b/docs/pages/development.rst index 5ff11dcf48..6c6c7328cf 100644 --- a/docs/pages/development.rst +++ b/docs/pages/development.rst @@ -28,8 +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 > crypto/api/v1/generated.go - oapi-codegen -generate types,server,client -package v1 docs/_static/did/v1.yaml > vdr/api/v1/generated.go + make gen-api Generating Mocks **************** @@ -38,7 +37,7 @@ These mocks are used by other modules .. code-block:: shell - mockgen -destination=crypto/mock/mock.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/makefile b/makefile index 9ddd273117..23ac533d61 100644 --- a/makefile +++ b/makefile @@ -6,11 +6,12 @@ gen-readme: ./generate_readme.sh gen-mocks: - mockgen -destination=crypto/mock/mock.go -package=mock -source=crypto/interface.go + 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 -package v1 docs/_static/did/v1.yaml > vdr/api/v1/generated.go + oapi-codegen -generate types,server,client -package v1 docs/_static/vdr/v1.yaml > vdr/api/v1/generated.go gen-docs: go run ./docs diff --git a/vdr/api/v1/api.go b/vdr/api/v1/api.go index bc52ed91a9..8b7edbdc5a 100644 --- a/vdr/api/v1/api.go +++ b/vdr/api/v1/api.go @@ -20,32 +20,78 @@ package v1 import ( + "errors" + "fmt" + "net/http" + "github.com/labstack/echo/v4" + did2 "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-network/pkg/model" "github.com/nuts-foundation/nuts-node/vdr/types" ) -// ApiWrapper is needed to connect the implementation to the echo ServiceWrapper -type ApiWrapper struct { - R types.VDR +// Wrapper is needed to connect the implementation to the echo ServiceWrapper +type Wrapper struct { + VDR types.VDR } -func (a ApiWrapper) SearchDID(ctx echo.Context, params SearchDIDParams) error { - panic("implement me") -} +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 + } -func (a ApiWrapper) CreateDID(ctx echo.Context) error { - panic("implement me") + // this API returns a DIDDocument according to spec so it may return the business object + return ctx.JSON(http.StatusOK, *doc) } -func (a ApiWrapper) GetDID(ctx echo.Context, didOrTag string) error { - panic("implement me") -} +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())) + } -func (a ApiWrapper) UpdateDID(ctx echo.Context, didOrTag string) error { - panic("implement me") -} + // 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, + } -func (a ApiWrapper) UpdateDIDTags(ctx echo.Context, didOrTag string) error { - panic("implement me") + return ctx.JSON(http.StatusOK, resolutionResult) } +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 := model.ParseHash(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, req.DocumentMetadata); 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..e444fd6600 --- /dev/null +++ b/vdr/api/v1/api_test.go @@ -0,0 +1,249 @@ +/* + * 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, + } + + 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, nil, 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, + DocumentMetadata: types.DocumentMetadata{}, + CurrentHash: "0000000000000000000000000000000000000000", + } + + 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, + DocumentMetadata: types.DocumentMetadata{}, + 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 index 0d54bd620a..5ad56966cf 100644 --- a/vdr/api/v1/client.go +++ b/vdr/api/v1/client.go @@ -26,6 +26,8 @@ import ( "io" "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-network/pkg/model" + "github.com/nuts-foundation/nuts-node/vdr/types" "io/ioutil" "net/http" @@ -33,13 +35,13 @@ import ( "time" ) -// HttpClient holds the server address and other basic settings for the http client -type HttpClient struct { +// 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 { +func (hb HTTPClient) client() ClientInterface { url := hb.ServerAddress if !strings.Contains(url, "http") { url = fmt.Sprintf("http://%v", hb.ServerAddress) @@ -52,47 +54,47 @@ func (hb HttpClient) client() ClientInterface { return response } -func (hb HttpClient) Search(onlyOwn bool, tags []string) ([]did.Document, error) { - panic("implement me") -} - -func (hb HttpClient) Create() (*did.Document, error) { +func (hb HTTPClient) Create() (*did.Document, error) { if response, err := hb.client().CreateDID(context.Background()); err != nil { return nil, err - } else if err := testResponseCode(http.StatusCreated, response); err != nil { + } else if err := testResponseCode(http.StatusOK, response); err != nil { return nil, err } else { return readDIDDocument(response.Body) } } -func (hb HttpClient) Get(DID did.DID) (*did.Document, *did.DocumentMetadata, error) { - return hb.get(DID.String()) -} - -func (hb HttpClient) GetByTag(tag string) (*did.Document, *did.DocumentMetadata, error) { - return hb.get("tag:" + tag) -} - -func (hb HttpClient) get(identifier string) (*did.Document, *did.DocumentMetadata, error) { - response, err := hb.client().GetDID(context.Background(), identifier) +func (hb HTTPClient) Get(DID did.DID) (*did.Document, *types.DocumentMetadata, error) { + response, err := hb.client().GetDID(context.Background(), DID.String()) if err != nil { return nil, nil, err } if err := testResponseCode(http.StatusOK, response); err != nil { return nil, nil, err - } else { - // TODO: Parse response - return nil, nil, nil } -} -func (hb HttpClient) Update(DID did.DID, hash []byte, nextVersion did.Document) (*did.Document, error) { - panic("implement me") + if resolutionResult, err := readDIDResolutionResult(response.Body); err != nil { + return nil, nil, err + } else { + return resolutionResult.Document, resolutionResult.DocumentMetadata, nil + } } -func (hb HttpClient) Tag(DID did.DID, tags []string) error { - panic("implement me") +func (hb HTTPClient) Update(DID did.DID, hash model.Hash, next did.Document, meta types.DocumentMetadata) (*did.Document, error) { + requestBody := UpdateDIDJSONRequestBody{ + "document": next, + "documentMetadata": meta, + "currentHash": hash.String(), + } + response, err := hb.client().UpdateDID(context.Background(), DID.String(), requestBody) + if err != nil { + return nil, err + } + if err := testResponseCode(http.StatusOK, response); err != nil { + return nil, err + } else { + return readDIDDocument(response.Body) + } } func testResponseCode(expectedStatusCode int, response *http.Response) error { @@ -115,3 +117,15 @@ func readDIDDocument(reader io.Reader) (*did.Document, error) { return &document, nil } } + +func readDIDResolutionResult(reader io.Reader) (*DIDResolutionResult, error) { + if data, err := ioutil.ReadAll(reader); err != nil { + return nil, fmt.Errorf("unable to read DID Resolve response: %w", err) + } else { + 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..7ae8a967e7 --- /dev/null +++ b/vdr/api/v1/client_test.go @@ -0,0 +1,142 @@ +/* + * 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 ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + did2 "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/nuts-network/pkg/model" + "github.com/nuts-foundation/nuts-node/vdr/types" + "github.com/stretchr/testify/assert" +) + +type handler struct { + statusCode int + responseData interface{} +} + +func (h handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(h.statusCode) + bytes, _ := json.Marshal(h.responseData) + writer.Write(bytes) +} + +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(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 - other", func(t *testing.T) { + s := httptest.NewServer(handler{statusCode: http.StatusInternalServerError, responseData: ""}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + _, err := c.Create() + assert.Error(t, err) + }) +} + +func TestHttpClient_Get(t *testing.T) { + did, _ := did2.ParseDID("did:nuts:1") + didDoc := &did2.Document{ + ID: *did, + } + meta := &types.DocumentMetadata{} + + t.Run("ok", func(t *testing.T) { + resolutionResult := DIDResolutionResult { + Document: didDoc, + DocumentMetadata: meta, + } + s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: resolutionResult}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + doc, meta, err := c.Get(*did) + 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(handler{statusCode: http.StatusNotFound, responseData: ""}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + + _, _, err := c.Get(*did) + + assert.Error(t, err) + }) + + t.Run("error - invalid response", func(t *testing.T) { + s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: "}"}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + + _, _, err := c.Get(*did) + + assert.Error(t, err) + }) +} + +func TestHTTPClient_Update(t *testing.T) { + did, _ := did2.ParseDID("did:nuts:1") + didDoc := did2.Document{ + ID: *did, + } + meta := types.DocumentMetadata{} + hash, _ := model.ParseHash("0000000000000000000000000000000000000000") + + t.Run("ok", func(t *testing.T) { + s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: didDoc}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + doc, err := c.Update(*did, hash, didDoc, meta) + if !assert.NoError(t, err) { + return + } + assert.NotNil(t, doc) + }) + + t.Run("error - not found", func(t *testing.T) { + s := httptest.NewServer(handler{statusCode: http.StatusNotFound, responseData: ""}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + + _, err := c.Update(*did, hash, didDoc, meta) + + assert.Error(t, err) + }) + + t.Run("error - invalid response", func(t *testing.T) { + s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: "}"}) + c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} + + _, err := c.Update(*did, hash, didDoc, meta) + + assert.Error(t, err) + }) +} diff --git a/vdr/api/v1/generated.go b/vdr/api/v1/generated.go index 13cfc08602..03f5e39739 100644 --- a/vdr/api/v1/generated.go +++ b/vdr/api/v1/generated.go @@ -13,7 +13,6 @@ import ( "net/http" "net/url" "strings" - "time" "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/labstack/echo/v4" @@ -22,62 +21,12 @@ import ( // DIDDocument defines model for DIDDocument. type DIDDocument map[string]interface{} -// DIDDocumentMetadata defines model for DIDDocumentMetadata. -type DIDDocumentMetadata struct { - - // Date/time at which the document was originally created. - Created *time.Time `json:"created,omitempty"` - - // Hash (SHA-256, hex-encoded) of DID document bytes. Is equal to payloadHash in network layer. - Hash *string `json:"hash,omitempty"` - - // Hash (SHA-256, hex-encoded) of the JWS envelope of the first version of the DID document. - OriginJwsHash *string `json:"originJwsHash,omitempty"` - - // Date/time at which the document (or this version) was updated. - Updated *time.Time `json:"updated,omitempty"` - - // Semantic version of the DID document. - Version *int `json:"version,omitempty"` -} - -// DIDResolutionResult defines model for DIDResolutionResult. -type DIDResolutionResult struct { - - // The actual DID Document in JSON representation. - Document *DIDDocument `json:"document,omitempty"` - DocumentMetadata *DIDDocumentMetadata `json:"documentMetadata,omitempty"` - - // Metadata collected during DID Document (a.k.a. DID Resolution Metadata). - ResolutionMetadata *map[string]interface{} `json:"resolutionMetadata,omitempty"` -} - -// SearchDIDParams defines parameters for SearchDID. -type SearchDIDParams struct { - - // URL encoded DID or tag. When given a tag it must resolve to exactly one DID. - Tags string `json:"tags"` -} - // UpdateDIDJSONBody defines parameters for UpdateDID. -type UpdateDIDJSONBody struct { - - // SHA-256 hash of the last version of the DID Document - CurrentHash *string `json:"currentHash,omitempty"` - - // The actual DID Document in JSON representation. - Document *DIDDocument `json:"document,omitempty"` -} - -// UpdateDIDTagsJSONBody defines parameters for UpdateDIDTags. -type UpdateDIDTagsJSONBody []string +type UpdateDIDJSONBody map[string]interface{} // UpdateDIDRequestBody defines body for UpdateDID for application/json ContentType. type UpdateDIDJSONRequestBody UpdateDIDJSONBody -// UpdateDIDTagsRequestBody defines body for UpdateDIDTags for application/json ContentType. -type UpdateDIDTagsJSONRequestBody UpdateDIDTagsJSONBody - // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -151,39 +100,16 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { - // SearchDID request - SearchDID(ctx context.Context, params *SearchDIDParams) (*http.Response, error) - // CreateDID request CreateDID(ctx context.Context) (*http.Response, error) // GetDID request - GetDID(ctx context.Context, didOrTag string) (*http.Response, error) + GetDID(ctx context.Context, did string) (*http.Response, error) // UpdateDID request with any body - UpdateDIDWithBody(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*http.Response, error) + UpdateDIDWithBody(ctx context.Context, did string, contentType string, body io.Reader) (*http.Response, error) - UpdateDID(ctx context.Context, didOrTag string, body UpdateDIDJSONRequestBody) (*http.Response, error) - - // UpdateDIDTags request with any body - UpdateDIDTagsWithBody(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*http.Response, error) - - UpdateDIDTags(ctx context.Context, didOrTag string, body UpdateDIDTagsJSONRequestBody) (*http.Response, error) -} - -func (c *Client) SearchDID(ctx context.Context, params *SearchDIDParams) (*http.Response, error) { - req, err := NewSearchDIDRequest(c.Server, params) - 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) + UpdateDID(ctx context.Context, did string, body UpdateDIDJSONRequestBody) (*http.Response, error) } func (c *Client) CreateDID(ctx context.Context) (*http.Response, error) { @@ -201,38 +127,8 @@ func (c *Client) CreateDID(ctx context.Context) (*http.Response, error) { return c.Client.Do(req) } -func (c *Client) GetDID(ctx context.Context, didOrTag string) (*http.Response, error) { - req, err := NewGetDIDRequest(c.Server, didOrTag) - 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, didOrTag string, contentType string, body io.Reader) (*http.Response, error) { - req, err := NewUpdateDIDRequestWithBody(c.Server, didOrTag, 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, didOrTag string, body UpdateDIDJSONRequestBody) (*http.Response, error) { - req, err := NewUpdateDIDRequest(c.Server, didOrTag, body) +func (c *Client) GetDID(ctx context.Context, did string) (*http.Response, error) { + req, err := NewGetDIDRequest(c.Server, did) if err != nil { return nil, err } @@ -246,8 +142,8 @@ func (c *Client) UpdateDID(ctx context.Context, didOrTag string, body UpdateDIDJ return c.Client.Do(req) } -func (c *Client) UpdateDIDTagsWithBody(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*http.Response, error) { - req, err := NewUpdateDIDTagsRequestWithBody(c.Server, didOrTag, contentType, body) +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 } @@ -261,8 +157,8 @@ func (c *Client) UpdateDIDTagsWithBody(ctx context.Context, didOrTag string, con return c.Client.Do(req) } -func (c *Client) UpdateDIDTags(ctx context.Context, didOrTag string, body UpdateDIDTagsJSONRequestBody) (*http.Response, error) { - req, err := NewUpdateDIDTagsRequest(c.Server, didOrTag, body) +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 } @@ -276,49 +172,6 @@ func (c *Client) UpdateDIDTags(ctx context.Context, didOrTag string, body Update return c.Client.Do(req) } -// NewSearchDIDRequest generates requests for SearchDID -func NewSearchDIDRequest(server string, params *SearchDIDParams) (*http.Request, error) { - var err error - - queryUrl, err := url.Parse(server) - if err != nil { - return nil, err - } - - basePath := fmt.Sprintf("/internal/registry/v1/did") - if basePath[0] == '/' { - basePath = basePath[1:] - } - - queryUrl, err = queryUrl.Parse(basePath) - if err != nil { - return nil, err - } - - queryValues := queryUrl.Query() - - if queryFrag, err := runtime.StyleParam("form", true, "tags", params.Tags); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryUrl.RawQuery = queryValues.Encode() - - req, err := http.NewRequest("GET", queryUrl.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - // NewCreateDIDRequest generates requests for CreateDID func NewCreateDIDRequest(server string) (*http.Request, error) { var err error @@ -328,7 +181,7 @@ func NewCreateDIDRequest(server string) (*http.Request, error) { return nil, err } - basePath := fmt.Sprintf("/internal/registry/v1/did") + basePath := fmt.Sprintf("/internal/vdr/v1/did") if basePath[0] == '/' { basePath = basePath[1:] } @@ -347,12 +200,12 @@ func NewCreateDIDRequest(server string) (*http.Request, error) { } // NewGetDIDRequest generates requests for GetDID -func NewGetDIDRequest(server string, didOrTag string) (*http.Request, error) { +func NewGetDIDRequest(server string, did string) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParam("simple", false, "didOrTag", didOrTag) + pathParam0, err = runtime.StyleParam("simple", false, "did", did) if err != nil { return nil, err } @@ -362,7 +215,7 @@ func NewGetDIDRequest(server string, didOrTag string) (*http.Request, error) { return nil, err } - basePath := fmt.Sprintf("/internal/registry/v1/did/%s", pathParam0) + basePath := fmt.Sprintf("/internal/vdr/v1/did/%s", pathParam0) if basePath[0] == '/' { basePath = basePath[1:] } @@ -381,23 +234,23 @@ func NewGetDIDRequest(server string, didOrTag string) (*http.Request, error) { } // NewUpdateDIDRequest calls the generic UpdateDID builder with application/json body -func NewUpdateDIDRequest(server string, didOrTag string, body UpdateDIDJSONRequestBody) (*http.Request, error) { +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, didOrTag, "application/json", bodyReader) + return NewUpdateDIDRequestWithBody(server, did, "application/json", bodyReader) } // NewUpdateDIDRequestWithBody generates requests for UpdateDID with any type of body -func NewUpdateDIDRequestWithBody(server string, didOrTag string, contentType string, body io.Reader) (*http.Request, error) { +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, "didOrTag", didOrTag) + pathParam0, err = runtime.StyleParam("simple", false, "did", did) if err != nil { return nil, err } @@ -407,7 +260,7 @@ func NewUpdateDIDRequestWithBody(server string, didOrTag string, contentType str return nil, err } - basePath := fmt.Sprintf("/internal/registry/v1/did/%s", pathParam0) + basePath := fmt.Sprintf("/internal/vdr/v1/did/%s", pathParam0) if basePath[0] == '/' { basePath = basePath[1:] } @@ -426,52 +279,6 @@ func NewUpdateDIDRequestWithBody(server string, didOrTag string, contentType str return req, nil } -// NewUpdateDIDTagsRequest calls the generic UpdateDIDTags builder with application/json body -func NewUpdateDIDTagsRequest(server string, didOrTag string, body UpdateDIDTagsJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewUpdateDIDTagsRequestWithBody(server, didOrTag, "application/json", bodyReader) -} - -// NewUpdateDIDTagsRequestWithBody generates requests for UpdateDIDTags with any type of body -func NewUpdateDIDTagsRequestWithBody(server string, didOrTag string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParam("simple", false, "didOrTag", didOrTag) - if err != nil { - return nil, err - } - - queryUrl, err := url.Parse(server) - if err != nil { - return nil, err - } - - basePath := fmt.Sprintf("/internal/registry/v1/did/%s/tag", pathParam0) - if basePath[0] == '/' { - basePath = basePath[1:] - } - - queryUrl, err = queryUrl.Parse(basePath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", 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 @@ -501,46 +308,16 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // SearchDID request - SearchDIDWithResponse(ctx context.Context, params *SearchDIDParams) (*SearchDIDResponse, error) - // CreateDID request CreateDIDWithResponse(ctx context.Context) (*CreateDIDResponse, error) // GetDID request - GetDIDWithResponse(ctx context.Context, didOrTag string) (*GetDIDResponse, error) + GetDIDWithResponse(ctx context.Context, did string) (*GetDIDResponse, error) // UpdateDID request with any body - UpdateDIDWithBodyWithResponse(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*UpdateDIDResponse, error) - - UpdateDIDWithResponse(ctx context.Context, didOrTag string, body UpdateDIDJSONRequestBody) (*UpdateDIDResponse, error) - - // UpdateDIDTags request with any body - UpdateDIDTagsWithBodyWithResponse(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*UpdateDIDTagsResponse, error) - - UpdateDIDTagsWithResponse(ctx context.Context, didOrTag string, body UpdateDIDTagsJSONRequestBody) (*UpdateDIDTagsResponse, error) -} - -type SearchDIDResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *[]string -} + UpdateDIDWithBodyWithResponse(ctx context.Context, did string, contentType string, body io.Reader) (*UpdateDIDResponse, error) -// Status returns HTTPResponse.Status -func (r SearchDIDResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r SearchDIDResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 + UpdateDIDWithResponse(ctx context.Context, did string, body UpdateDIDJSONRequestBody) (*UpdateDIDResponse, error) } type CreateDIDResponse struct { @@ -567,7 +344,7 @@ func (r CreateDIDResponse) StatusCode() int { type GetDIDResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *DIDResolutionResult + JSON200 *map[string]interface{} } // Status returns HTTPResponse.Status @@ -607,36 +384,6 @@ func (r UpdateDIDResponse) StatusCode() int { return 0 } -type UpdateDIDTagsResponse struct { - Body []byte - HTTPResponse *http.Response -} - -// Status returns HTTPResponse.Status -func (r UpdateDIDTagsResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r UpdateDIDTagsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// SearchDIDWithResponse request returning *SearchDIDResponse -func (c *ClientWithResponses) SearchDIDWithResponse(ctx context.Context, params *SearchDIDParams) (*SearchDIDResponse, error) { - rsp, err := c.SearchDID(ctx, params) - if err != nil { - return nil, err - } - return ParseSearchDIDResponse(rsp) -} - // CreateDIDWithResponse request returning *CreateDIDResponse func (c *ClientWithResponses) CreateDIDWithResponse(ctx context.Context) (*CreateDIDResponse, error) { rsp, err := c.CreateDID(ctx) @@ -647,8 +394,8 @@ func (c *ClientWithResponses) CreateDIDWithResponse(ctx context.Context) (*Creat } // GetDIDWithResponse request returning *GetDIDResponse -func (c *ClientWithResponses) GetDIDWithResponse(ctx context.Context, didOrTag string) (*GetDIDResponse, error) { - rsp, err := c.GetDID(ctx, didOrTag) +func (c *ClientWithResponses) GetDIDWithResponse(ctx context.Context, did string) (*GetDIDResponse, error) { + rsp, err := c.GetDID(ctx, did) if err != nil { return nil, err } @@ -656,65 +403,22 @@ func (c *ClientWithResponses) GetDIDWithResponse(ctx context.Context, didOrTag s } // UpdateDIDWithBodyWithResponse request with arbitrary body returning *UpdateDIDResponse -func (c *ClientWithResponses) UpdateDIDWithBodyWithResponse(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*UpdateDIDResponse, error) { - rsp, err := c.UpdateDIDWithBody(ctx, didOrTag, contentType, body) +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, didOrTag string, body UpdateDIDJSONRequestBody) (*UpdateDIDResponse, error) { - rsp, err := c.UpdateDID(ctx, didOrTag, body) +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) } -// UpdateDIDTagsWithBodyWithResponse request with arbitrary body returning *UpdateDIDTagsResponse -func (c *ClientWithResponses) UpdateDIDTagsWithBodyWithResponse(ctx context.Context, didOrTag string, contentType string, body io.Reader) (*UpdateDIDTagsResponse, error) { - rsp, err := c.UpdateDIDTagsWithBody(ctx, didOrTag, contentType, body) - if err != nil { - return nil, err - } - return ParseUpdateDIDTagsResponse(rsp) -} - -func (c *ClientWithResponses) UpdateDIDTagsWithResponse(ctx context.Context, didOrTag string, body UpdateDIDTagsJSONRequestBody) (*UpdateDIDTagsResponse, error) { - rsp, err := c.UpdateDIDTags(ctx, didOrTag, body) - if err != nil { - return nil, err - } - return ParseUpdateDIDTagsResponse(rsp) -} - -// ParseSearchDIDResponse parses an HTTP response from a SearchDIDWithResponse call -func ParseSearchDIDResponse(rsp *http.Response) (*SearchDIDResponse, error) { - bodyBytes, err := ioutil.ReadAll(rsp.Body) - defer rsp.Body.Close() - if err != nil { - return nil, err - } - - response := &SearchDIDResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []string - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - - return response, nil -} - // ParseCreateDIDResponse parses an HTTP response from a CreateDIDWithResponse call func ParseCreateDIDResponse(rsp *http.Response) (*CreateDIDResponse, error) { bodyBytes, err := ioutil.ReadAll(rsp.Body) @@ -749,7 +453,7 @@ func ParseGetDIDResponse(rsp *http.Response) (*GetDIDResponse, error) { switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest DIDResolutionResult + var dest map[string]interface{} if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -779,42 +483,17 @@ func ParseUpdateDIDResponse(rsp *http.Response) (*UpdateDIDResponse, error) { return response, nil } -// ParseUpdateDIDTagsResponse parses an HTTP response from a UpdateDIDTagsWithResponse call -func ParseUpdateDIDTagsResponse(rsp *http.Response) (*UpdateDIDTagsResponse, error) { - bodyBytes, err := ioutil.ReadAll(rsp.Body) - defer rsp.Body.Close() - if err != nil { - return nil, err - } - - response := &UpdateDIDTagsResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - } - - return response, nil -} - // ServerInterface represents all server handlers. type ServerInterface interface { - // Searches for Nuts DIDs - // (GET /internal/registry/v1/did) - SearchDID(ctx echo.Context, params SearchDIDParams) error // Creates a new Nuts DID - // (POST /internal/registry/v1/did) + // (POST /internal/vdr/v1/did) CreateDID(ctx echo.Context) error // Resolves a Nuts DID Document - // (GET /internal/registry/v1/did/{didOrTag}) - GetDID(ctx echo.Context, didOrTag string) error + // (GET /internal/vdr/v1/did/{did}) + GetDID(ctx echo.Context, did string) error // Updates a Nuts DID Document - // (PUT /internal/registry/v1/did/{didOrTag}) - UpdateDID(ctx echo.Context, didOrTag string) error - // Replaces the tags of the DID Document. - // (POST /internal/registry/v1/did/{didOrTag}/tag) - UpdateDIDTags(ctx echo.Context, didOrTag string) error + // (PUT /internal/vdr/v1/did/{did}) + UpdateDID(ctx echo.Context, did string) error } // ServerInterfaceWrapper converts echo contexts to parameters. @@ -822,24 +501,6 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } -// SearchDID converts echo context to params. -func (w *ServerInterfaceWrapper) SearchDID(ctx echo.Context) error { - var err error - - // Parameter object where we will unmarshal all parameters from the context - var params SearchDIDParams - // ------------- Required query parameter "tags" ------------- - - err = runtime.BindQueryParameter("form", true, true, "tags", ctx.QueryParams(), ¶ms.Tags) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter tags: %s", err)) - } - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.SearchDID(ctx, params) - return err -} - // CreateDID converts echo context to params. func (w *ServerInterfaceWrapper) CreateDID(ctx echo.Context) error { var err error @@ -852,48 +513,32 @@ func (w *ServerInterfaceWrapper) CreateDID(ctx echo.Context) error { // GetDID converts echo context to params. func (w *ServerInterfaceWrapper) GetDID(ctx echo.Context) error { var err error - // ------------- Path parameter "didOrTag" ------------- - var didOrTag string + // ------------- Path parameter "did" ------------- + var did string - err = runtime.BindStyledParameter("simple", false, "didOrTag", ctx.Param("didOrTag"), &didOrTag) + err = runtime.BindStyledParameter("simple", false, "did", ctx.Param("did"), &did) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter didOrTag: %s", err)) + 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, didOrTag) + 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 "didOrTag" ------------- - var didOrTag string - - err = runtime.BindStyledParameter("simple", false, "didOrTag", ctx.Param("didOrTag"), &didOrTag) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter didOrTag: %s", err)) - } - - // Invoke the callback with all the unmarshalled arguments - err = w.Handler.UpdateDID(ctx, didOrTag) - return err -} - -// UpdateDIDTags converts echo context to params. -func (w *ServerInterfaceWrapper) UpdateDIDTags(ctx echo.Context) error { - var err error - // ------------- Path parameter "didOrTag" ------------- - var didOrTag string + // ------------- Path parameter "did" ------------- + var did string - err = runtime.BindStyledParameter("simple", false, "didOrTag", ctx.Param("didOrTag"), &didOrTag) + err = runtime.BindStyledParameter("simple", false, "did", ctx.Param("did"), &did) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter didOrTag: %s", err)) + 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.UpdateDIDTags(ctx, didOrTag) + err = w.Handler.UpdateDID(ctx, did) return err } @@ -925,11 +570,9 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } - router.GET(baseURL+"/internal/registry/v1/did", wrapper.SearchDID) - router.POST(baseURL+"/internal/registry/v1/did", wrapper.CreateDID) - router.GET(baseURL+"/internal/registry/v1/did/:didOrTag", wrapper.GetDID) - router.PUT(baseURL+"/internal/registry/v1/did/:didOrTag", wrapper.UpdateDID) - router.POST(baseURL+"/internal/registry/v1/did/:didOrTag/tag", wrapper.UpdateDIDTags) + 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..86e6363961 --- /dev/null +++ b/vdr/api/v1/types.go @@ -0,0 +1,34 @@ +/* + * 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" +) + +// DIDResolutionResult is a convenience type for marshalling/unmarshalling +type DIDResolutionResult struct { + Document *did.Document `json: "document,omitempty"` + DocumentMetadata *types.DocumentMetadata `json: "documentMetadata,omitempty"` +} + +// DIDUpdateRequest is a convenience type for updating a DID +type DIDUpdateRequest struct { + Document did.Document `json: "document"` + DocumentMetadata types.DocumentMetadata `json: "documentMetadata,omitempty"` + CurrentHash string `json: "currentHash"` +} diff --git a/vdr/config.go b/vdr/config.go index 468b10a8d8..8826cceb87 100644 --- a/vdr/config.go +++ b/vdr/config.go @@ -46,4 +46,3 @@ func DefaultRegistryConfig() Config { ClientTimeout: 10, } } - diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go index 34ba712c7d..e3cff791fc 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -47,7 +47,7 @@ func NewRegistryEngine() *core.Engine { FlagSet: flagSet(), Name: pkg.ModuleName, Routes: func(router core.EchoRouter) { - api.RegisterHandlers(router, &api.ApiWrapper{R: r}) + api.RegisterHandlers(router, &api.Wrapper{VDR: r}) }, Start: r.Start, Shutdown: r.Shutdown, @@ -60,7 +60,7 @@ func flagSet() *pflag.FlagSet { defs := vdr.DefaultRegistryConfig() flagSet.String(vdr.ConfDataDir, defs.Datadir, fmt.Sprintf("Location of data files, default: %s", defs.Datadir)) - flagSet.String(vdr.ConfMode, defs.Mode, fmt.Sprintf("server or client, when client it uses the HttpClient, default: %s", defs.Mode)) + flagSet.String(vdr.ConfMode, defs.Mode, fmt.Sprintf("server or client, when client it uses the HTTPClient, default: %s", defs.Mode)) flagSet.String(vdr.ConfAddress, defs.Address, fmt.Sprintf("Interface and port for http server to bind to, default: %s", defs.Address)) 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)) diff --git a/vdr/store/memory.go b/vdr/store/memory.go index df5636a1fd..967f8c8708 100644 --- a/vdr/store/memory.go +++ b/vdr/store/memory.go @@ -60,9 +60,9 @@ type memory struct { } type memoryEntry struct { - document did.Document - metadata types.DocumentMetadata - next *memoryEntry + document did.Document + metadata types.DocumentMetadata + next *memoryEntry } func (me memoryEntry) isDeactivated() bool { @@ -84,14 +84,14 @@ func (m *memory) Resolve(DID did.DID, metadata *types.ResolveMetaData) (*did.Doc if metadata != nil { // filter on hash if metadata.Hash != nil { - entries = entries.filter(func (e memoryEntry) bool{ + 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{ + entries = entries.filter(func(e memoryEntry) bool { return !e.isDeactivated() }) } diff --git a/vdr/store/memory_test.go b/vdr/store/memory_test.go index 7082fb843d..9733c93f7b 100644 --- a/vdr/store/memory_test.go +++ b/vdr/store/memory_test.go @@ -52,7 +52,7 @@ func TestMemory_Resolve(t *testing.T) { store := NewMemoryStore() did1, _ := did.ParseDID("did:nuts:1") doc := did.Document{ - ID: *did1, + ID: *did1, Controller: []did.DID{*did1}, } @@ -60,7 +60,7 @@ func TestMemory_Resolve(t *testing.T) { h, _ := model.ParseHash("0000000000000000000000000000000000000000") meta := types.DocumentMetadata{ Created: time.Now().Add(time.Hour * -24), - Hash: h, + Hash: h, } _ = store.Write(doc, meta) @@ -171,7 +171,7 @@ func TestMemory_Update(t *testing.T) { store := NewMemoryStore() did1, _ := did.ParseDID("did:nuts:1") doc := did.Document{ - ID: *did1, + ID: *did1, Controller: []did.DID{*did1}, } h, _ := model.ParseHash("0000000000000000000000000000000000000000") @@ -189,7 +189,7 @@ func TestMemory_Update(t *testing.T) { t.Run("updates the previous record", func(t *testing.T) { later := time.Now().Add(time.Hour * 24) meta = types.DocumentMetadata{ - Hash: h, + Hash: h, Created: time.Now(), Updated: &later, } @@ -218,7 +218,7 @@ func TestMemory_Update(t *testing.T) { ID: *did1, } err := store.Write(doc, meta) - if ! assert.NoError(t, err) { + if !assert.NoError(t, err) { return } diff --git a/vdr/types/common.go b/vdr/types/common.go index 712e5d7ab9..e351182777 100644 --- a/vdr/types/common.go +++ b/vdr/types/common.go @@ -23,19 +23,22 @@ import ( ) var ErrUpdateOnOutdatedData = errors.New("could not update outdated document") + // ErrInvalidDID The DID supplied to the DID resolution function does not conform to valid syntax.0 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 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"` + Created time.Time `json:"created"` Updated *time.Time `json:"updated,omitempty"` // Version contains the semantic version of the DID document. Version int `json:"version"` diff --git a/vdr/types/mock.go b/vdr/types/mock.go new file mode 100644 index 0000000000..12a7a0f2d0 --- /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" + model "github.com/nuts-foundation/nuts-network/pkg/model" + 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(DID go_did.DID, metadata *ResolveMetaData) (*go_did.Document, *DocumentMetadata, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Resolve", DID, 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(DID, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resolve", reflect.TypeOf((*MockDocResolver)(nil).Resolve), DID, 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(DIDDocument go_did.Document, metadata DocumentMetadata) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", DIDDocument, metadata) + ret0, _ := ret[0].(error) + return ret0 +} + +// Write indicates an expected call of Write +func (mr *MockDocWriterMockRecorder) Write(DIDDocument, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockDocWriter)(nil).Write), DIDDocument, 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(DID go_did.DID, hash model.Hash, next go_did.Document, metadata DocumentMetadata) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", DID, hash, next, metadata) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update +func (mr *MockDocUpdaterMockRecorder) Update(DID, hash, next, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDocUpdater)(nil).Update), DID, hash, 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(DID go_did.DID, hash model.Hash) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Deactivate", DID, hash) +} + +// Deactivate indicates an expected call of Deactivate +func (mr *MockDocDeactivatorMockRecorder) Deactivate(DID, hash interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deactivate", reflect.TypeOf((*MockDocDeactivator)(nil).Deactivate), DID, hash) +} + +// 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(DID go_did.DID, metadata *ResolveMetaData) (*go_did.Document, *DocumentMetadata, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Resolve", DID, 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(DID, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resolve", reflect.TypeOf((*MockStore)(nil).Resolve), DID, metadata) +} + +// Write mocks base method +func (m *MockStore) Write(DIDDocument go_did.Document, metadata DocumentMetadata) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", DIDDocument, metadata) + ret0, _ := ret[0].(error) + return ret0 +} + +// Write indicates an expected call of Write +func (mr *MockStoreMockRecorder) Write(DIDDocument, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockStore)(nil).Write), DIDDocument, metadata) +} + +// Update mocks base method +func (m *MockStore) Update(DID go_did.DID, hash model.Hash, next go_did.Document, metadata DocumentMetadata) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", DID, hash, next, metadata) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update +func (mr *MockStoreMockRecorder) Update(DID, hash, next, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockStore)(nil).Update), DID, hash, 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(DID go_did.DID, metadata *ResolveMetaData) (*go_did.Document, *DocumentMetadata, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Resolve", DID, 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(DID, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resolve", reflect.TypeOf((*MockVDR)(nil).Resolve), DID, 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(DID go_did.DID, hash model.Hash, next go_did.Document, metadata DocumentMetadata) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", DID, hash, next, metadata) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update +func (mr *MockVDRMockRecorder) Update(DID, hash, next, metadata interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockVDR)(nil).Update), DID, hash, next, metadata) +} + +// Deactivate mocks base method +func (m *MockVDR) Deactivate(DID go_did.DID, hash model.Hash) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Deactivate", DID, hash) +} + +// Deactivate indicates an expected call of Deactivate +func (mr *MockVDRMockRecorder) Deactivate(DID, hash interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deactivate", reflect.TypeOf((*MockVDR)(nil).Deactivate), DID, hash) +} diff --git a/vdr/vdr.go b/vdr/vdr.go index 280817348a..a06b06d42d 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -180,4 +180,3 @@ func (r Registry) Update(dID did.DID, hash model.Hash, next did.Document, metada func (r *Registry) Deactivate(DID did.DID, hash model.Hash) { panic("implement me") } - From 77d59882626acfe50e7566a5bbd281aceeed0bdb Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Wed, 20 Jan 2021 16:01:58 +0100 Subject: [PATCH 27/54] working server with create-did CLI command --- cmd/root.go | 5 +++ vdr/api/v1/client.go | 2 +- vdr/doc-creator.go | 1 + vdr/engine/engine.go | 64 +++++++++++++++------------------------ vdr/engine/engine_test.go | 8 ----- 5 files changed, 32 insertions(+), 48 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 001e00e06f..0895ce49fd 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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.NewRegistryEngine()) + } func injectConfig(cfg *core.NutsGlobalConfig) { diff --git a/vdr/api/v1/client.go b/vdr/api/v1/client.go index 5ad56966cf..ccaa8c479e 100644 --- a/vdr/api/v1/client.go +++ b/vdr/api/v1/client.go @@ -112,7 +112,7 @@ func readDIDDocument(reader io.Reader) (*did.Document, error) { } else { document := did.Document{} if err := json.Unmarshal(data, &document); err != nil { - return nil, fmt.Errorf("unable to unmarshal DID Document response: %w", err) + return nil, fmt.Errorf("unable to unmarshal DID Document response: %w, %s", err, string(data)) } return &document, nil } diff --git a/vdr/doc-creator.go b/vdr/doc-creator.go index 57c5fbbeb8..4c31f6deb1 100644 --- a/vdr/doc-creator.go +++ b/vdr/doc-creator.go @@ -73,6 +73,7 @@ func (n NutsDocCreator) Create() (*did.Document, error) { didID.Fragment = "" verificationMethod, err := keyToVerificationMethod(key, keyID) + verificationMethod.Controller = *didID doc := &did.Document{ Context: []did.URI{did.DIDContextV1URI()}, diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go index e3cff791fc..1135ae7cf6 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -20,11 +20,10 @@ package engine import ( + "encoding/json" "fmt" "github.com/nuts-foundation/nuts-network/pkg" - "github.com/nuts-foundation/nuts-node/vdr/logging" - "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/vdr" api "github.com/nuts-foundation/nuts-node/vdr/api/v1" @@ -43,7 +42,7 @@ func NewRegistryEngine() *core.Engine { Cmd: cmd(), Configure: r.Configure, Config: &r.Config, - ConfigKey: "registry", + ConfigKey: "vdr", FlagSet: flagSet(), Name: pkg.ModuleName, Routes: func(router core.EchoRouter) { @@ -69,38 +68,36 @@ func flagSet() *pflag.FlagSet { func cmd() *cobra.Command { cmd := &cobra.Command{ - Use: "registry", - Short: "registry commands", + Use: "vdr", + Short: "Verifiable Data Registry commands", } - cmd.AddCommand(&cobra.Command{ - Use: "version", - Short: "Print the version number of the Nuts registry", - Run: func(cmd *cobra.Command, args []string) { - logging.Log().Errorf("version 0.0.0") - }, - }) - - cmd.AddCommand(&cobra.Command{ - Use: "search [tags]", - Short: "Find DIDs within the registry that have the given tags (comma-separated)", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - // split tags on comma and call Search - - //logging.Log().Errorf("Found %d organizations\n", len(os)) - }, - }) - cmd.AddCommand(&cobra.Command{ Use: "create-did", Short: "Registers a new DID", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { - - // call Create - - //logging.Log().Info("DID registered.") + core.NutsConfig().ServerAddress() + + // client + client := api.HTTPClient{ + ServerAddress: core.NutsConfig().ServerAddress(), + Timeout: 5, + } + + doc, err := client.Create() + if err != nil { + fmt.Printf("Failed to create DID: %s\n", err.Error()) + return nil + } + + bytes, err := json.MarshalIndent(doc, "", " ") + if err != nil { + fmt.Printf("Failed to display DID document:\n%s\nRaw: %v", err.Error(), *doc) + return nil + } + + fmt.Printf("Created DID document: %v\n", string(bytes)) return nil }, }) @@ -116,17 +113,6 @@ func cmd() *cobra.Command { }, }) - cmd.AddCommand(&cobra.Command{ - Use: "tag DID [tags]", - Short: "Replace the tags of the given DID document with the given tags (comma-separated)", - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - - // call Tag - return nil - }, - }) - cmd.AddCommand(&cobra.Command{ Use: "update DID [file]", Short: "Update a DID with the given DID document, this replaces the DID document. If no file is given, stdin is used", diff --git a/vdr/engine/engine_test.go b/vdr/engine/engine_test.go index 5c4e380d6c..9afc14ff0b 100644 --- a/vdr/engine/engine_test.go +++ b/vdr/engine/engine_test.go @@ -30,14 +30,6 @@ func TestSearchOrg(t *testing.T) { // Register test instance singleton } -func TestPrintVersion(t *testing.T) { - // Register test instance singleton - command := cmd() - command.SetArgs([]string{"version"}) - err := command.Execute() - assert.NoError(t, err) -} - func Test_flagSet(t *testing.T) { assert.NotNil(t, flagSet()) } From c4f2544d9f304e9e5b2c19eff86af4f8dd56ac55 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Wed, 20 Jan 2021 16:07:01 +0100 Subject: [PATCH 28/54] copyright and gofmt --- cmd/root.go | 4 ++-- core/config.go | 4 ++-- core/config_test.go | 4 ++-- core/constants.go | 4 ++-- core/diagnostics.go | 4 ++-- core/diagnostics_test.go | 4 ++-- core/engine.go | 4 ++-- core/engine_test.go | 4 ++-- core/logging.go | 4 ++-- core/metrics.go | 4 ++-- core/metrics_test.go | 4 ++-- core/status.go | 4 ++-- crypto/api/v1/api.go | 4 ++-- crypto/api/v1/api_test.go | 4 ++-- crypto/api/v1/client.go | 4 ++-- crypto/api/v1/client_test.go | 4 ++-- crypto/api/v1/generated.go | 1 - crypto/api/v1/generated_test.go | 4 ++-- crypto/crypto.go | 4 ++-- crypto/crypto_test.go | 4 ++-- crypto/engine/engine.go | 4 ++-- crypto/engine/engine_test.go | 4 ++-- crypto/interface.go | 4 ++-- crypto/jwx.go | 4 ++-- crypto/jwx_test.go | 4 ++-- crypto/log/log.go | 2 +- crypto/storage/fs.go | 4 ++-- crypto/storage/storage.go | 4 ++-- crypto/util/common.go | 4 ++-- crypto/util/pem_test.go | 4 ++-- main.go | 4 ++-- vdr/api/v1/client.go | 4 ++-- vdr/api/v1/client_test.go | 2 +- vdr/api/v1/generated.go | 1 - vdr/logging/log.go | 2 +- 35 files changed, 63 insertions(+), 65 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 0895ce49fd..1ec4c0b1ae 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 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..73dfd4bdff 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 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..ec23261054 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 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..526cea71a5 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 diff --git a/core/metrics.go b/core/metrics.go index 5ce62697aa..0f1ec359a7 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 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..ac2e9fd951 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 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 7ae5a66459..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 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 cee6a0d18a..e39292278a 100644 --- a/crypto/api/v1/generated.go +++ b/crypto/api/v1/generated.go @@ -463,4 +463,3 @@ 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 e2072733db..cdcf6b5290 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.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/crypto_test.go b/crypto/crypto_test.go index 58d9508131..4f857ca471 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 diff --git a/crypto/engine/engine.go b/crypto/engine/engine.go index e7c54010c7..f260c2e227 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 diff --git a/crypto/engine/engine_test.go b/crypto/engine/engine_test.go index 7ebbb66c9f..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 diff --git a/crypto/interface.go b/crypto/interface.go index 50c837bc87..611b26daf5 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 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 f4ab7f9cc5..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 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/storage/fs.go b/crypto/storage/fs.go index 5b509b6426..58e177b5ec 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 communityy * * 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/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/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/vdr/api/v1/client.go b/vdr/api/v1/client.go index ccaa8c479e..48cf3b3fc2 100644 --- a/vdr/api/v1/client.go +++ b/vdr/api/v1/client.go @@ -82,9 +82,9 @@ func (hb HTTPClient) Get(DID did.DID) (*did.Document, *types.DocumentMetadata, e func (hb HTTPClient) Update(DID did.DID, hash model.Hash, next did.Document, meta types.DocumentMetadata) (*did.Document, error) { requestBody := UpdateDIDJSONRequestBody{ - "document": next, + "document": next, "documentMetadata": meta, - "currentHash": hash.String(), + "currentHash": hash.String(), } response, err := hb.client().UpdateDID(context.Background(), DID.String(), requestBody) if err != nil { diff --git a/vdr/api/v1/client_test.go b/vdr/api/v1/client_test.go index 7ae8a967e7..4bd88131ff 100644 --- a/vdr/api/v1/client_test.go +++ b/vdr/api/v1/client_test.go @@ -71,7 +71,7 @@ func TestHttpClient_Get(t *testing.T) { meta := &types.DocumentMetadata{} t.Run("ok", func(t *testing.T) { - resolutionResult := DIDResolutionResult { + resolutionResult := DIDResolutionResult{ Document: didDoc, DocumentMetadata: meta, } diff --git a/vdr/api/v1/generated.go b/vdr/api/v1/generated.go index 03f5e39739..365a8da69f 100644 --- a/vdr/api/v1/generated.go +++ b/vdr/api/v1/generated.go @@ -575,4 +575,3 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.PUT(baseURL+"/internal/vdr/v1/did/:did", wrapper.UpdateDID) } - diff --git a/vdr/logging/log.go b/vdr/logging/log.go index f15168021f..4e3fdc4891 100644 --- a/vdr/logging/log.go +++ b/vdr/logging/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 From f6cfdac705047f65e346e30f5b925b9547d68336 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Wed, 20 Jan 2021 16:28:11 +0100 Subject: [PATCH 29/54] added timeoout to api client ctx --- vdr/api/v1/client.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/vdr/api/v1/client.go b/vdr/api/v1/client.go index 48cf3b3fc2..11f86cd928 100644 --- a/vdr/api/v1/client.go +++ b/vdr/api/v1/client.go @@ -55,7 +55,10 @@ func (hb HTTPClient) client() ClientInterface { } func (hb HTTPClient) Create() (*did.Document, error) { - if response, err := hb.client().CreateDID(context.Background()); err != nil { + 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 @@ -65,7 +68,10 @@ func (hb HTTPClient) Create() (*did.Document, error) { } func (hb HTTPClient) Get(DID did.DID) (*did.Document, *types.DocumentMetadata, error) { - response, err := hb.client().GetDID(context.Background(), DID.String()) + ctx, cancel := hb.withTimeout() + defer cancel() + + response, err := hb.client().GetDID(ctx, DID.String()) if err != nil { return nil, nil, err } @@ -81,12 +87,15 @@ func (hb HTTPClient) Get(DID did.DID) (*did.Document, *types.DocumentMetadata, e } func (hb HTTPClient) Update(DID did.DID, hash model.Hash, next did.Document, meta types.DocumentMetadata) (*did.Document, error) { + ctx, cancel := hb.withTimeout() + defer cancel() + requestBody := UpdateDIDJSONRequestBody{ "document": next, "documentMetadata": meta, "currentHash": hash.String(), } - response, err := hb.client().UpdateDID(context.Background(), DID.String(), requestBody) + response, err := hb.client().UpdateDID(ctx, DID.String(), requestBody) if err != nil { return nil, err } @@ -97,6 +106,10 @@ func (hb HTTPClient) Update(DID did.DID, hash model.Hash, next did.Document, met } } +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) From 4e4b87271f71bb6f95d762d975453fb1eff7412b Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Wed, 20 Jan 2021 17:10:49 +0100 Subject: [PATCH 30/54] added CLI resolve --- vdr/engine/engine.go | 48 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go index 1135ae7cf6..823b3ba94f 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -22,7 +22,9 @@ package engine import ( "encoding/json" "fmt" + "time" + "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/nuts-network/pkg" "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/vdr" @@ -77,13 +79,7 @@ func cmd() *cobra.Command { Short: "Registers a new DID", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { - core.NutsConfig().ServerAddress() - - // client - client := api.HTTPClient{ - ServerAddress: core.NutsConfig().ServerAddress(), - Timeout: 5, - } + client := httpClient() doc, err := client.Create() if err != nil { @@ -93,7 +89,7 @@ func cmd() *cobra.Command { bytes, err := json.MarshalIndent(doc, "", " ") if err != nil { - fmt.Printf("Failed to display DID document:\n%s\nRaw: %v", err.Error(), *doc) + fmt.Printf("Failed to display DID document: %s\n", err.Error()) return nil } @@ -103,12 +99,33 @@ func cmd() *cobra.Command { }) cmd.AddCommand(&cobra.Command{ - Use: "get [DID or tag]", - Short: "Find a DID document based on its DID or tag", + 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() + + did, err := did.ParseDID(args[0]) + if err != nil { + fmt.Printf("Failed to parse DID: %s\n", err.Error()) + return nil + } + + doc, meta, err := client.Get(*did) + if err != nil { + fmt.Printf("Failed to resolve DID document: %s\n", err.Error()) + return nil + } + + for _, o := range []interface{}{doc, meta} { + bytes, err := json.MarshalIndent(o, "", " ") + if err != nil { + fmt.Printf("Failed to display object: %s\n", err.Error()) + return nil + } + fmt.Printf("%s\n", string(bytes)) + } - // call Get or GetByTag return nil }, }) @@ -128,3 +145,12 @@ func cmd() *cobra.Command { return cmd } + +func httpClient() api.HTTPClient { + core.NutsConfig().ServerAddress() + + return api.HTTPClient{ + ServerAddress: core.NutsConfig().ServerAddress(), + Timeout: 5 * time.Second, + } +} From 0299f549b8da51942f706beaead159c9518ef597 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Wed, 20 Jan 2021 17:13:30 +0100 Subject: [PATCH 31/54] wrong codestyle badge --- README.rst | 2 +- README_template.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index f6920ff9b4..c6fecdefd6 100644 --- a/README.rst +++ b/README.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 diff --git a/README_template.rst b/README_template.rst index adbff17ee3..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 From d660cca4d3da9430973b32e7e8e963899a23bcb9 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Wed, 20 Jan 2021 17:14:22 +0100 Subject: [PATCH 32/54] README upd --- README.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index c6fecdefd6..35d0dcf025 100644 --- a/README.rst +++ b/README.rst @@ -42,8 +42,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 > crypto/api/v1/generated.go - oapi-codegen -generate types,server,client -package v1 docs/_static/did/v1.yaml > vdr/api/v1/generated.go + make gen-api Generating Mocks **************** @@ -52,7 +51,7 @@ These mocks are used by other modules .. code-block:: shell - mockgen -destination=crypto/mock/mock.go -package=mock -source=crypto/interface.go KeyStore + 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. From 4b1b1b981ffcf6b2296f8cfe07c6fc0efe893f0a Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Wed, 20 Jan 2021 17:19:15 +0100 Subject: [PATCH 33/54] swagger UI upd --- docs/pages/api.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pages/api.rst b/docs/pages/api.rst index 4e97ac9375..11bba41834 100644 --- a/docs/pages/api.rst +++ b/docs/pages/api.rst @@ -14,8 +14,8 @@ Nuts APIs const ui = SwaggerUIBundle({ "dom_id": "#swagger-ui", urls: [ - {url: "../_static/crypto/v1.yaml", name: "example"}, - {url: "../_static/did/v1.yaml", name: "example"}, + {url: "../_static/crypto/v1.yaml", name: "crypto"}, + {url: "../_static/vdr/v1.yaml", name: "Verifiable data registry"}, ], presets: [ SwaggerUIBundle.presets.apis, From 1b45a14c43ba150da83d45d57c2088367fc2557f Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Thu, 21 Jan 2021 14:44:56 +0100 Subject: [PATCH 34/54] Added model.Hash to crypto/hash Added Update cmd to CLI --- crypto/api/v1/generated.go | 1 + crypto/hash/hash.go | 141 ++++++++++++++++++++++++++++++++ crypto/hash/hash_test.go | 160 +++++++++++++++++++++++++++++++++++++ docs/_static/vdr/v1.yaml | 2 +- vdr/api/v1/api.go | 6 +- vdr/api/v1/api_test.go | 4 +- vdr/api/v1/client.go | 7 +- vdr/api/v1/client_test.go | 11 ++- vdr/api/v1/generated.go | 1 + vdr/api/v1/types.go | 1 - vdr/engine/engine.go | 69 ++++++++++++++-- vdr/store/memory.go | 8 +- vdr/store/memory_test.go | 18 ++--- vdr/types/common.go | 8 +- vdr/types/interface.go | 6 +- vdr/types/mock.go | 42 +++++----- vdr/vdr.go | 8 +- 17 files changed, 425 insertions(+), 68 deletions(-) create mode 100644 crypto/hash/hash.go create mode 100644 crypto/hash/hash_test.go 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/hash/hash.go b/crypto/hash/hash.go new file mode 100644 index 0000000000..9dcbf91738 --- /dev/null +++ b/crypto/hash/hash.go @@ -0,0 +1,141 @@ +/* + * 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 ( + "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 hexidecimal 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/hash_test.go b/crypto/hash/hash_test.go new file mode 100644 index 0000000000..bc75df749e --- /dev/null +++ b/crypto/hash/hash_test.go @@ -0,0 +1,160 @@ +/* + * 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_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()) + }) +} + +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/docs/_static/vdr/v1.yaml b/docs/_static/vdr/v1.yaml index fd2bdc4afe..7b4fb0bf26 100644 --- a/docs/_static/vdr/v1.yaml +++ b/docs/_static/vdr/v1.yaml @@ -77,7 +77,7 @@ paths: application/json: schema: type: object - description: JSON object with document, documentMetadata and currentHash + description: JSON object with document and currentHash responses: "200": description: "DID Document has been updated." diff --git a/vdr/api/v1/api.go b/vdr/api/v1/api.go index 8b7edbdc5a..fd2c3f8e3d 100644 --- a/vdr/api/v1/api.go +++ b/vdr/api/v1/api.go @@ -26,7 +26,7 @@ import ( "github.com/labstack/echo/v4" did2 "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/nuts-network/pkg/model" + "github.com/nuts-foundation/nuts-node/crypto/hash" "github.com/nuts-foundation/nuts-node/vdr/types" ) @@ -80,12 +80,12 @@ func (a Wrapper) UpdateDID(ctx echo.Context, did string) error { return ctx.String(http.StatusBadRequest, fmt.Sprintf("given update request could not be parsed: %s", err.Error())) } - h, err := model.ParseHash(req.CurrentHash) + 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, req.DocumentMetadata); err != nil { + 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) diff --git a/vdr/api/v1/api_test.go b/vdr/api/v1/api_test.go index e444fd6600..caa4512a8b 100644 --- a/vdr/api/v1/api_test.go +++ b/vdr/api/v1/api_test.go @@ -126,8 +126,7 @@ func TestWrapper_UpdateDID(t *testing.T) { } didUpdate := DIDUpdateRequest{ Document: *didDoc, - DocumentMetadata: types.DocumentMetadata{}, - CurrentHash: "0000000000000000000000000000000000000000", + CurrentHash: "452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620", } t.Run("ok", func(t *testing.T) { @@ -201,7 +200,6 @@ func TestWrapper_UpdateDID(t *testing.T) { didUpdate := DIDUpdateRequest{ Document: *didDoc, - DocumentMetadata: types.DocumentMetadata{}, CurrentHash: "0", } diff --git a/vdr/api/v1/client.go b/vdr/api/v1/client.go index 11f86cd928..8ff1a09054 100644 --- a/vdr/api/v1/client.go +++ b/vdr/api/v1/client.go @@ -26,7 +26,7 @@ import ( "io" "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/nuts-network/pkg/model" + "github.com/nuts-foundation/nuts-node/crypto/hash" "github.com/nuts-foundation/nuts-node/vdr/types" "io/ioutil" @@ -86,14 +86,13 @@ func (hb HTTPClient) Get(DID did.DID) (*did.Document, *types.DocumentMetadata, e } } -func (hb HTTPClient) Update(DID did.DID, hash model.Hash, next did.Document, meta types.DocumentMetadata) (*did.Document, error) { +func (hb HTTPClient) Update(DID did.DID, current hash.SHA256Hash, next did.Document) (*did.Document, error) { ctx, cancel := hb.withTimeout() defer cancel() requestBody := UpdateDIDJSONRequestBody{ "document": next, - "documentMetadata": meta, - "currentHash": hash.String(), + "currentHash": current.String(), } response, err := hb.client().UpdateDID(ctx, DID.String(), requestBody) if err != nil { diff --git a/vdr/api/v1/client_test.go b/vdr/api/v1/client_test.go index 4bd88131ff..90f585ef9a 100644 --- a/vdr/api/v1/client_test.go +++ b/vdr/api/v1/client_test.go @@ -23,7 +23,7 @@ import ( "time" did2 "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/nuts-network/pkg/model" + "github.com/nuts-foundation/nuts-node/crypto/hash" "github.com/nuts-foundation/nuts-node/vdr/types" "github.com/stretchr/testify/assert" ) @@ -109,13 +109,12 @@ func TestHTTPClient_Update(t *testing.T) { didDoc := did2.Document{ ID: *did, } - meta := types.DocumentMetadata{} - hash, _ := model.ParseHash("0000000000000000000000000000000000000000") + hash, _ := hash.ParseHex("0000000000000000000000000000000000000000") t.Run("ok", func(t *testing.T) { s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: didDoc}) c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} - doc, err := c.Update(*did, hash, didDoc, meta) + doc, err := c.Update(*did, hash, didDoc) if !assert.NoError(t, err) { return } @@ -126,7 +125,7 @@ func TestHTTPClient_Update(t *testing.T) { s := httptest.NewServer(handler{statusCode: http.StatusNotFound, responseData: ""}) c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} - _, err := c.Update(*did, hash, didDoc, meta) + _, err := c.Update(*did, hash, didDoc) assert.Error(t, err) }) @@ -135,7 +134,7 @@ func TestHTTPClient_Update(t *testing.T) { s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: "}"}) c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} - _, err := c.Update(*did, hash, didDoc, meta) + _, err := c.Update(*did, hash, didDoc) assert.Error(t, err) }) diff --git a/vdr/api/v1/generated.go b/vdr/api/v1/generated.go index 365a8da69f..03f5e39739 100644 --- a/vdr/api/v1/generated.go +++ b/vdr/api/v1/generated.go @@ -575,3 +575,4 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.PUT(baseURL+"/internal/vdr/v1/did/:did", wrapper.UpdateDID) } + diff --git a/vdr/api/v1/types.go b/vdr/api/v1/types.go index 86e6363961..71490413fe 100644 --- a/vdr/api/v1/types.go +++ b/vdr/api/v1/types.go @@ -29,6 +29,5 @@ type DIDResolutionResult struct { // DIDUpdateRequest is a convenience type for updating a DID type DIDUpdateRequest struct { Document did.Document `json: "document"` - DocumentMetadata types.DocumentMetadata `json: "documentMetadata,omitempty"` CurrentHash string `json: "currentHash"` } diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go index 823b3ba94f..8cc5bf43b7 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -20,13 +20,18 @@ package engine import ( + "bufio" "encoding/json" + "errors" "fmt" + "io/ioutil" + "os" "time" "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/nuts-network/pkg" "github.com/nuts-foundation/nuts-node/core" + "github.com/nuts-foundation/nuts-node/crypto/hash" "github.com/nuts-foundation/nuts-node/vdr" api "github.com/nuts-foundation/nuts-node/vdr/api/v1" "github.com/spf13/cobra" @@ -131,14 +136,56 @@ func cmd() *cobra.Command { }) cmd.AddCommand(&cobra.Command{ - Use: "update DID [file]", - Short: "Update a DID with the given DID document, this replaces the DID document. If no file is given, stdin is used", - Args: cobra.RangeArgs(1, 2), + 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() - // read from file or stdin + // DID + d, err := did.ParseDID(args[0]) + if err != nil { + fmt.Printf("Failed to parse DID: %s\n", err.Error()) + return nil + } + + // Hash + h, err := hash.ParseHex(args[1]) + if err != nil { + fmt.Printf("Failed to parse hash: %s\n", err.Error()) + return nil + } + + var bytes []byte + if len(args) == 3 { + // read from file + bytes, err = ioutil.ReadFile(args[2]) + if err != nil { + fmt.Printf("Failed to read file %s: %s\n", args[2], err.Error()) + return nil + } + } else { + // read from stdin + bytes, err = readFromStdin() + if err != nil { + fmt.Printf("Failed to read from pipe: %s\n", err.Error()) + return nil + } + } + + // parse + var didDoc did.Document + if err = json.Unmarshal(bytes, &didDoc); err != nil { + fmt.Printf("Failed to parse DID document: %s\n", err.Error()) + return nil + } + + if _, err = client.Update(*d, h, didDoc); err != nil { + fmt.Printf("Failed to update DID document: %s\n", err.Error()) + return nil + } - // call Update return nil }, }) @@ -146,6 +193,18 @@ func cmd() *cobra.Command { return cmd } +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") + } else { + return ioutil.ReadAll(bufio.NewReader(os.Stdin)) + } +} + func httpClient() api.HTTPClient { core.NutsConfig().ServerAddress() diff --git a/vdr/store/memory.go b/vdr/store/memory.go index 967f8c8708..6d34a98cf4 100644 --- a/vdr/store/memory.go +++ b/vdr/store/memory.go @@ -19,7 +19,7 @@ import ( "sync" "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/nuts-network/pkg/model" + "github.com/nuts-foundation/nuts-node/crypto/hash" "github.com/nuts-foundation/nuts-node/vdr/types" ) @@ -155,7 +155,7 @@ func (m *memory) Write(DIDDocument did.Document, metadata types.DocumentMetadata // Update 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(DID did.DID, hash model.Hash, next did.Document, metadata types.DocumentMetadata) error { +func (m *memory) Update(DID did.DID, current hash.SHA256Hash, next did.Document, metadata *types.DocumentMetadata) error { m.mutex.Lock() defer m.mutex.Unlock() @@ -172,13 +172,13 @@ func (m *memory) Update(DID did.DID, hash model.Hash, next did.Document, metadat } // hashes must match - if !hash.Equals(entry.metadata.Hash) { + if !current.Equals(entry.metadata.Hash) { return types.ErrUpdateOnOutdatedData } newEntry := &memoryEntry{ document: next, - metadata: metadata, + metadata: *metadata, } // update next in last document diff --git a/vdr/store/memory_test.go b/vdr/store/memory_test.go index 9733c93f7b..654be165c6 100644 --- a/vdr/store/memory_test.go +++ b/vdr/store/memory_test.go @@ -20,7 +20,7 @@ import ( "time" "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/nuts-network/pkg/model" + "github.com/nuts-foundation/nuts-node/crypto/hash" "github.com/nuts-foundation/nuts-node/vdr/types" "github.com/stretchr/testify/assert" ) @@ -57,7 +57,7 @@ func TestMemory_Resolve(t *testing.T) { } //upd := time.Now().Add(time.Hour * 24) - h, _ := model.ParseHash("0000000000000000000000000000000000000000") + h, _ := hash.ParseHex("452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620") meta := types.DocumentMetadata{ Created: time.Now().Add(time.Hour * -24), Hash: h, @@ -174,7 +174,7 @@ func TestMemory_Update(t *testing.T) { ID: *did1, Controller: []did.DID{*did1}, } - h, _ := model.ParseHash("0000000000000000000000000000000000000000") + h, _ := hash.ParseHex("452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620") meta := types.DocumentMetadata{ Hash: h, } @@ -182,7 +182,7 @@ func TestMemory_Update(t *testing.T) { _ = store.Write(doc, meta) t.Run("returns no error on success", func(t *testing.T) { - err := store.Update(*did1, h, doc, meta) + err := store.Update(*did1, h, doc, &meta) assert.NoError(t, err) }) @@ -193,7 +193,7 @@ func TestMemory_Update(t *testing.T) { Created: time.Now(), Updated: &later, } - err := store.Update(*did1, h, doc, meta) + err := store.Update(*did1, h, doc, &meta) assert.NoError(t, err) s := store.(*memory) @@ -202,13 +202,13 @@ func TestMemory_Update(t *testing.T) { 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) + 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, _ := model.ParseHash("0000000000000000000000000000000000000001") - err := store.Update(*did1, h, doc, meta) + h, _ := hash.ParseHex("452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4621") + err := store.Update(*did1, h, doc, &meta) assert.Equal(t, types.ErrUpdateOnOutdatedData, err) }) @@ -222,7 +222,7 @@ func TestMemory_Update(t *testing.T) { return } - err = store.Update(*did1, h, doc, meta) + err = store.Update(*did1, h, doc, &meta) assert.Equal(t, types.ErrDeactivated, err) }) } diff --git a/vdr/types/common.go b/vdr/types/common.go index e351182777..cc1302099b 100644 --- a/vdr/types/common.go +++ b/vdr/types/common.go @@ -19,7 +19,7 @@ import ( "errors" "time" - "github.com/nuts-foundation/nuts-network/pkg/model" + "github.com/nuts-foundation/nuts-node/crypto/hash" ) var ErrUpdateOnOutdatedData = errors.New("could not update outdated document") @@ -43,9 +43,9 @@ type DocumentMetadata struct { // 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 model.Hash `json:"originJwsHash"` + OriginJWSHash hash.SHA256Hash `json:"originJwsHash"` // Hash of DID document bytes. Is equal to payloadHash in network layer. - Hash model.Hash `json:"hash"` + Hash hash.SHA256Hash `json:"hash"` } // ResolveMetaData contains metadata for the resolver. @@ -53,7 +53,7 @@ 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 *model.Hash + Hash *hash.SHA256Hash // Allow DIDs which are deactivated AllowDeactivated bool } diff --git a/vdr/types/interface.go b/vdr/types/interface.go index 377ea352c7..b395093706 100644 --- a/vdr/types/interface.go +++ b/vdr/types/interface.go @@ -17,7 +17,7 @@ package types import ( "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/nuts-network/pkg/model" + "github.com/nuts-foundation/nuts-node/crypto/hash" ) // DocResolver is the interface that groups all the DID Document read methods @@ -52,7 +52,7 @@ type DocUpdater interface { // 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(DID did.DID, hash model.Hash, next did.Document, metadata DocumentMetadata) error + Update(DID did.DID, current hash.SHA256Hash, next did.Document, metadata *DocumentMetadata) error } // DocDeactivator is the interface that defines functions to deactivate DID Docs @@ -62,7 +62,7 @@ type DocDeactivator interface { // 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(DID did.DID, hash model.Hash) + Deactivate(DID did.DID, current hash.SHA256Hash) } // Store is the interface that groups all low level VDR DID storage operations. diff --git a/vdr/types/mock.go b/vdr/types/mock.go index 12a7a0f2d0..9db473bd29 100644 --- a/vdr/types/mock.go +++ b/vdr/types/mock.go @@ -7,7 +7,7 @@ package types import ( gomock "github.com/golang/mock/gomock" go_did "github.com/nuts-foundation/go-did" - model "github.com/nuts-foundation/nuts-network/pkg/model" + hash "github.com/nuts-foundation/nuts-node/crypto/hash" reflect "reflect" ) @@ -149,17 +149,17 @@ func (m *MockDocUpdater) EXPECT() *MockDocUpdaterMockRecorder { } // Update mocks base method -func (m *MockDocUpdater) Update(DID go_did.DID, hash model.Hash, next go_did.Document, metadata DocumentMetadata) error { +func (m *MockDocUpdater) Update(DID go_did.DID, current hash.SHA256Hash, next go_did.Document, metadata *DocumentMetadata) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Update", DID, hash, next, metadata) + ret := m.ctrl.Call(m, "Update", DID, current, next, metadata) ret0, _ := ret[0].(error) return ret0 } // Update indicates an expected call of Update -func (mr *MockDocUpdaterMockRecorder) Update(DID, hash, next, metadata interface{}) *gomock.Call { +func (mr *MockDocUpdaterMockRecorder) Update(DID, current, next, metadata interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDocUpdater)(nil).Update), DID, hash, next, metadata) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDocUpdater)(nil).Update), DID, current, next, metadata) } // MockDocDeactivator is a mock of DocDeactivator interface @@ -186,15 +186,15 @@ func (m *MockDocDeactivator) EXPECT() *MockDocDeactivatorMockRecorder { } // Deactivate mocks base method -func (m *MockDocDeactivator) Deactivate(DID go_did.DID, hash model.Hash) { +func (m *MockDocDeactivator) Deactivate(DID go_did.DID, current hash.SHA256Hash) { m.ctrl.T.Helper() - m.ctrl.Call(m, "Deactivate", DID, hash) + m.ctrl.Call(m, "Deactivate", DID, current) } // Deactivate indicates an expected call of Deactivate -func (mr *MockDocDeactivatorMockRecorder) Deactivate(DID, hash interface{}) *gomock.Call { +func (mr *MockDocDeactivatorMockRecorder) Deactivate(DID, current interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deactivate", reflect.TypeOf((*MockDocDeactivator)(nil).Deactivate), DID, hash) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deactivate", reflect.TypeOf((*MockDocDeactivator)(nil).Deactivate), DID, current) } // MockStore is a mock of Store interface @@ -251,17 +251,17 @@ func (mr *MockStoreMockRecorder) Write(DIDDocument, metadata interface{}) *gomoc } // Update mocks base method -func (m *MockStore) Update(DID go_did.DID, hash model.Hash, next go_did.Document, metadata DocumentMetadata) error { +func (m *MockStore) Update(DID go_did.DID, current hash.SHA256Hash, next go_did.Document, metadata *DocumentMetadata) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Update", DID, hash, next, metadata) + ret := m.ctrl.Call(m, "Update", DID, current, next, metadata) ret0, _ := ret[0].(error) return ret0 } // Update indicates an expected call of Update -func (mr *MockStoreMockRecorder) Update(DID, hash, next, metadata interface{}) *gomock.Call { +func (mr *MockStoreMockRecorder) Update(DID, current, next, metadata interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockStore)(nil).Update), DID, hash, next, metadata) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockStore)(nil).Update), DID, current, next, metadata) } // MockVDR is a mock of VDR interface @@ -319,27 +319,27 @@ func (mr *MockVDRMockRecorder) Create() *gomock.Call { } // Update mocks base method -func (m *MockVDR) Update(DID go_did.DID, hash model.Hash, next go_did.Document, metadata DocumentMetadata) error { +func (m *MockVDR) Update(DID go_did.DID, current hash.SHA256Hash, next go_did.Document, metadata *DocumentMetadata) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Update", DID, hash, next, metadata) + ret := m.ctrl.Call(m, "Update", DID, current, next, metadata) ret0, _ := ret[0].(error) return ret0 } // Update indicates an expected call of Update -func (mr *MockVDRMockRecorder) Update(DID, hash, next, metadata interface{}) *gomock.Call { +func (mr *MockVDRMockRecorder) Update(DID, current, next, metadata interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockVDR)(nil).Update), DID, hash, next, metadata) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockVDR)(nil).Update), DID, current, next, metadata) } // Deactivate mocks base method -func (m *MockVDR) Deactivate(DID go_did.DID, hash model.Hash) { +func (m *MockVDR) Deactivate(DID go_did.DID, current hash.SHA256Hash) { m.ctrl.T.Helper() - m.ctrl.Call(m, "Deactivate", DID, hash) + m.ctrl.Call(m, "Deactivate", DID, current) } // Deactivate indicates an expected call of Deactivate -func (mr *MockVDRMockRecorder) Deactivate(DID, hash interface{}) *gomock.Call { +func (mr *MockVDRMockRecorder) Deactivate(DID, current interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deactivate", reflect.TypeOf((*MockVDR)(nil).Deactivate), DID, hash) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deactivate", reflect.TypeOf((*MockVDR)(nil).Deactivate), DID, current) } diff --git a/vdr/vdr.go b/vdr/vdr.go index a06b06d42d..cbea677bb5 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -29,7 +29,6 @@ import ( "time" "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/nuts-network/pkg/model" "github.com/sirupsen/logrus" "github.com/nuts-foundation/nuts-node/vdr/store" @@ -46,6 +45,7 @@ import ( "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/crypto" + "github.com/nuts-foundation/nuts-node/crypto/hash" ) //type StoreWrapper struct { @@ -173,10 +173,10 @@ func (r Registry) Resolve(dID did.DID, metadata *types.ResolveMetaData) (*did.Do return r.store.Resolve(dID, metadata) } -func (r Registry) Update(dID did.DID, hash model.Hash, next did.Document, metadata types.DocumentMetadata) error { - return r.store.Update(dID, hash, next, metadata) +func (r Registry) Update(dID did.DID, current hash.SHA256Hash, next did.Document, metadata *types.DocumentMetadata) error { + return r.store.Update(dID, current, next, metadata) } -func (r *Registry) Deactivate(DID did.DID, hash model.Hash) { +func (r *Registry) Deactivate(DID did.DID, current hash.SHA256Hash) { panic("implement me") } From 71143eccba75446706f2d9b237dc177c95b2c839 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Thu, 21 Jan 2021 15:01:04 +0100 Subject: [PATCH 35/54] no annotations in codecov --- codecov.yml | 2 ++ 1 file changed, 2 insertions(+) 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 From f2e6ac4bb7d540c9e139614f0e6582de37cf9625 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Thu, 21 Jan 2021 15:38:55 +0100 Subject: [PATCH 36/54] Add tests fof didKidNamingFunc --- vdr/doc-creator_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/vdr/doc-creator_test.go b/vdr/doc-creator_test.go index 1705d47e55..27e50e7edc 100644 --- a/vdr/doc-creator_test.go +++ b/vdr/doc-creator_test.go @@ -2,6 +2,10 @@ package vdr import ( "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" "testing" "github.com/lestrrat-go/jwx/jwk" @@ -51,3 +55,30 @@ func TestDocCreator_Create(t *testing.T) { }) }) } + +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) + + }) +} From f475d0f703f5d242b344077cfb8a2eadb28d0858 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Thu, 21 Jan 2021 15:54:17 +0100 Subject: [PATCH 37/54] alias API generated types for DIDDocument and DIDDocumentMetadata --- docs/_static/vdr/v1.yaml | 32 ++++++++++++++++++++------ makefile | 2 +- vdr/api/v1/api.go | 4 ++-- vdr/api/v1/api_test.go | 3 ++- vdr/api/v1/client.go | 14 +++++------- vdr/api/v1/client_test.go | 16 ++++++------- vdr/api/v1/generated.go | 31 ++++++++++++++++--------- vdr/api/v1/types.go | 14 ++++-------- vdr/engine/engine.go | 48 ++++++++++++--------------------------- 9 files changed, 82 insertions(+), 82 deletions(-) diff --git a/docs/_static/vdr/v1.yaml b/docs/_static/vdr/v1.yaml index 7b4fb0bf26..670f912d56 100644 --- a/docs/_static/vdr/v1.yaml +++ b/docs/_static/vdr/v1.yaml @@ -45,9 +45,7 @@ paths: description: "DID has been found and returned." content: application/json: - schema: - type: object - description: A DIDResolutionResult is returned. + $ref: '#/components/schemas/DIDResolutionResult' "400": description: "given DID is incorrect." content: @@ -76,8 +74,7 @@ paths: content: application/json: schema: - type: object - description: JSON object with document and currentHash + $ref: '#/components/schemas/DIDUpdateRequest' responses: "200": description: "DID Document has been updated." @@ -108,6 +105,27 @@ components: schemas: DIDDocument: type: object - description: The actual DID Document in JSON representation. - DIDResolutionResult: + description: The actual DID Document. + DIDDocumentMetadata: type: object + description: The DID Document metadata. + DIDResolutionResult: + required: + - document + - documentMetadata + properties: + document: + $ref: '#/components/schemas/DIDDocument' + documentMetadata: + $ref: '#/components/schemas/DIDDocumentMetadata' + DIDUpdateRequest: + required: + - document + - currentHash + properties: + document: + $ref: '#/components/schemas/DIDDocument' + currentHash: + type: string + description: "hex encoded hash" + diff --git a/makefile b/makefile index 23ac533d61..be2aac1987 100644 --- a/makefile +++ b/makefile @@ -11,7 +11,7 @@ gen-mocks: 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 -package v1 docs/_static/vdr/v1.yaml > vdr/api/v1/generated.go + oapi-codegen -generate types,server,client,skip-prune -package v1 -exclude-schemas DIDDocument,DIDDocumentMetadata docs/_static/vdr/v1.yaml > vdr/api/v1/generated.go gen-docs: go run ./docs diff --git a/vdr/api/v1/api.go b/vdr/api/v1/api.go index fd2c3f8e3d..00fd07c7f8 100644 --- a/vdr/api/v1/api.go +++ b/vdr/api/v1/api.go @@ -62,8 +62,8 @@ func (a Wrapper) GetDID(ctx echo.Context, did string) error { } resolutionResult := DIDResolutionResult{ - Document: doc, - DocumentMetadata: meta, + Document: *doc, + DocumentMetadata: *meta, } return ctx.JSON(http.StatusOK, resolutionResult) diff --git a/vdr/api/v1/api_test.go b/vdr/api/v1/api_test.go index caa4512a8b..782dc6b514 100644 --- a/vdr/api/v1/api_test.go +++ b/vdr/api/v1/api_test.go @@ -67,6 +67,7 @@ func TestWrapper_GetDID(t *testing.T) { didDoc := &did2.Document{ ID: *did, } + meta := &types.DocumentMetadata{} t.Run("ok", func(t *testing.T) { ctx := newMockContext(t) @@ -78,7 +79,7 @@ func TestWrapper_GetDID(t *testing.T) { return nil }) - ctx.vdr.EXPECT().Resolve(*did, nil).Return(didDoc, nil, nil) + ctx.vdr.EXPECT().Resolve(*did, nil).Return(didDoc, meta, nil) err := ctx.client.GetDID(ctx.echo, did.String()) if !assert.NoError(t, err) { diff --git a/vdr/api/v1/client.go b/vdr/api/v1/client.go index 8ff1a09054..2f4f343235 100644 --- a/vdr/api/v1/client.go +++ b/vdr/api/v1/client.go @@ -26,8 +26,6 @@ import ( "io" "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/nuts-node/crypto/hash" - "github.com/nuts-foundation/nuts-node/vdr/types" "io/ioutil" "net/http" @@ -67,7 +65,7 @@ func (hb HTTPClient) Create() (*did.Document, error) { } } -func (hb HTTPClient) Get(DID did.DID) (*did.Document, *types.DocumentMetadata, error) { +func (hb HTTPClient) Get(DID did.DID) (*DIDDocument, *DIDDocumentMetadata, error) { ctx, cancel := hb.withTimeout() defer cancel() @@ -82,19 +80,19 @@ func (hb HTTPClient) Get(DID did.DID) (*did.Document, *types.DocumentMetadata, e if resolutionResult, err := readDIDResolutionResult(response.Body); err != nil { return nil, nil, err } else { - return resolutionResult.Document, resolutionResult.DocumentMetadata, nil + return &resolutionResult.Document, &resolutionResult.DocumentMetadata, nil } } -func (hb HTTPClient) Update(DID did.DID, current hash.SHA256Hash, next did.Document) (*did.Document, error) { +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.String(), + Document: next, + CurrentHash: current, } - response, err := hb.client().UpdateDID(ctx, DID.String(), requestBody) + response, err := hb.client().UpdateDID(ctx, DID, requestBody) if err != nil { return nil, err } diff --git a/vdr/api/v1/client_test.go b/vdr/api/v1/client_test.go index 90f585ef9a..0eb756e020 100644 --- a/vdr/api/v1/client_test.go +++ b/vdr/api/v1/client_test.go @@ -23,7 +23,6 @@ import ( "time" did2 "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" ) @@ -65,10 +64,10 @@ func TestHTTPClient_Create(t *testing.T) { func TestHttpClient_Get(t *testing.T) { did, _ := did2.ParseDID("did:nuts:1") - didDoc := &did2.Document{ + didDoc := did2.Document{ ID: *did, } - meta := &types.DocumentMetadata{} + meta := types.DocumentMetadata{} t.Run("ok", func(t *testing.T) { resolutionResult := DIDResolutionResult{ @@ -105,16 +104,17 @@ func TestHttpClient_Get(t *testing.T) { } func TestHTTPClient_Update(t *testing.T) { - did, _ := did2.ParseDID("did:nuts:1") + didString := "did:nuts:1" + did, _ := did2.ParseDID(didString) didDoc := did2.Document{ ID: *did, } - hash, _ := hash.ParseHex("0000000000000000000000000000000000000000") + hash := "0000000000000000000000000000000000000000" t.Run("ok", func(t *testing.T) { s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: didDoc}) c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} - doc, err := c.Update(*did, hash, didDoc) + doc, err := c.Update(didString, hash, didDoc) if !assert.NoError(t, err) { return } @@ -125,7 +125,7 @@ func TestHTTPClient_Update(t *testing.T) { s := httptest.NewServer(handler{statusCode: http.StatusNotFound, responseData: ""}) c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} - _, err := c.Update(*did, hash, didDoc) + _, err := c.Update(didString, hash, didDoc) assert.Error(t, err) }) @@ -134,7 +134,7 @@ func TestHTTPClient_Update(t *testing.T) { s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: "}"}) c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} - _, err := c.Update(*did, hash, didDoc) + _, err := c.Update(didString, hash, didDoc) assert.Error(t, err) }) diff --git a/vdr/api/v1/generated.go b/vdr/api/v1/generated.go index 03f5e39739..25ddbeb9fc 100644 --- a/vdr/api/v1/generated.go +++ b/vdr/api/v1/generated.go @@ -18,11 +18,28 @@ import ( "github.com/labstack/echo/v4" ) -// DIDDocument defines model for DIDDocument. -type DIDDocument map[string]interface{} +// DIDResolutionResult defines model for DIDResolutionResult. +type DIDResolutionResult struct { + + // The actual DID Document. + Document DIDDocument `json:"document"` + + // The DID Document metadata. + DocumentMetadata DIDDocumentMetadata `json:"documentMetadata"` +} + +// DIDUpdateRequest defines model for DIDUpdateRequest. +type DIDUpdateRequest struct { + + // hex encoded hash + CurrentHash string `json:"currentHash"` + + // The actual DID Document. + Document DIDDocument `json:"document"` +} // UpdateDIDJSONBody defines parameters for UpdateDID. -type UpdateDIDJSONBody map[string]interface{} +type UpdateDIDJSONBody DIDUpdateRequest // UpdateDIDRequestBody defines body for UpdateDID for application/json ContentType. type UpdateDIDJSONRequestBody UpdateDIDJSONBody @@ -344,7 +361,6 @@ func (r CreateDIDResponse) StatusCode() int { type GetDIDResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *map[string]interface{} } // Status returns HTTPResponse.Status @@ -452,13 +468,6 @@ func ParseGetDIDResponse(rsp *http.Response) (*GetDIDResponse, error) { } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest map[string]interface{} - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - } return response, nil diff --git a/vdr/api/v1/types.go b/vdr/api/v1/types.go index 71490413fe..889adceb78 100644 --- a/vdr/api/v1/types.go +++ b/vdr/api/v1/types.go @@ -20,14 +20,8 @@ import ( "github.com/nuts-foundation/nuts-node/vdr/types" ) -// DIDResolutionResult is a convenience type for marshalling/unmarshalling -type DIDResolutionResult struct { - Document *did.Document `json: "document,omitempty"` - DocumentMetadata *types.DocumentMetadata `json: "documentMetadata,omitempty"` -} +// DIDDocument is an alias +type DIDDocument = did.Document -// DIDUpdateRequest is a convenience type for updating a DID -type DIDUpdateRequest struct { - Document did.Document `json: "document"` - CurrentHash string `json: "currentHash"` -} +// DIDDocumentMetadata is an alias +type DIDDocumentMetadata = types.DocumentMetadata diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go index 8cc5bf43b7..1db11c444d 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -31,7 +31,6 @@ import ( "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/nuts-network/pkg" "github.com/nuts-foundation/nuts-node/core" - "github.com/nuts-foundation/nuts-node/crypto/hash" "github.com/nuts-foundation/nuts-node/vdr" api "github.com/nuts-foundation/nuts-node/vdr/api/v1" "github.com/spf13/cobra" @@ -88,17 +87,15 @@ func cmd() *cobra.Command { doc, err := client.Create() if err != nil { - fmt.Printf("Failed to create DID: %s\n", err.Error()) - return nil + return fmt.Errorf("unable to create new DID: %v", err) } bytes, err := json.MarshalIndent(doc, "", " ") if err != nil { - fmt.Printf("Failed to display DID document: %s\n", err.Error()) - return nil + return fmt.Errorf("failed to display created DID document: %v", err) } - fmt.Printf("Created DID document: %v\n", string(bytes)) + cmd.Printf("Created DID document: %v\n", string(bytes)) return nil }, }) @@ -112,21 +109,18 @@ func cmd() *cobra.Command { did, err := did.ParseDID(args[0]) if err != nil { - fmt.Printf("Failed to parse DID: %s\n", err.Error()) - return nil + return fmt.Errorf("failed to parse DID: %v\n", err) } doc, meta, err := client.Get(*did) if err != nil { - fmt.Printf("Failed to resolve DID document: %s\n", err.Error()) - return nil + return fmt.Errorf("failed to resolve DID document: %v\n", err) } for _, o := range []interface{}{doc, meta} { bytes, err := json.MarshalIndent(o, "", " ") if err != nil { - fmt.Printf("Failed to display object: %s\n", err.Error()) - return nil + return fmt.Errorf("failed to display object: %v\n", err) } fmt.Printf("%s\n", string(bytes)) } @@ -143,47 +137,33 @@ func cmd() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { client := httpClient() - // DID - d, err := did.ParseDID(args[0]) - if err != nil { - fmt.Printf("Failed to parse DID: %s\n", err.Error()) - return nil - } - - // Hash - h, err := hash.ParseHex(args[1]) - if err != nil { - fmt.Printf("Failed to parse hash: %s\n", err.Error()) - return nil - } + d := args[0] + h := args[1] var bytes []byte + var err error if len(args) == 3 { // read from file bytes, err = ioutil.ReadFile(args[2]) if err != nil { - fmt.Printf("Failed to read file %s: %s\n", args[2], err.Error()) - return nil + return fmt.Errorf("failed to read file %s: %s\n", args[2], err) } } else { // read from stdin bytes, err = readFromStdin() if err != nil { - fmt.Printf("Failed to read from pipe: %s\n", err.Error()) - return nil + return fmt.Errorf("failed to read from pipe: %s\n", err) } } // parse var didDoc did.Document if err = json.Unmarshal(bytes, &didDoc); err != nil { - fmt.Printf("Failed to parse DID document: %s\n", err.Error()) - return nil + return fmt.Errorf("failed to parse DID document: %s\n", err) } - if _, err = client.Update(*d, h, didDoc); err != nil { - fmt.Printf("Failed to update DID document: %s\n", err.Error()) - return nil + if _, err = client.Update(d, h, didDoc); err != nil { + return fmt.Errorf("failed to update DID document: %s\n", err) } return nil From 86e8125b2e492efa0a37c4735248addd4cd01bc1 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Thu, 21 Jan 2021 16:18:43 +0100 Subject: [PATCH 38/54] added DIDDocument and DIDDocumentMetadata to OAS --- docs/_static/vdr/v1.yaml | 102 ++++++++++++++++++++++++++++++++++++++- makefile | 2 +- vdr/api/v1/generated.go | 4 +- 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/docs/_static/vdr/v1.yaml b/docs/_static/vdr/v1.yaml index 670f912d56..3aefe97364 100644 --- a/docs/_static/vdr/v1.yaml +++ b/docs/_static/vdr/v1.yaml @@ -105,10 +105,68 @@ components: schemas: DIDDocument: type: object - description: The actual DID Document. + description: The actual DID Document according to the w3c spec. + 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: Array of URIs + type: array + items: + type: string + controller: + description: Array 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: string DIDResolutionResult: required: - document @@ -118,6 +176,25 @@ components: $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 @@ -128,4 +205,27 @@ components: currentHash: type: string description: "hex encoded hash" + 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 as defined in rfc7517 + type: object + type: + description: the type of the key + example: "JsonWebKey2020" + type: string + diff --git a/makefile b/makefile index be2aac1987..2adf167ff9 100644 --- a/makefile +++ b/makefile @@ -11,7 +11,7 @@ gen-mocks: 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 docs/_static/vdr/v1.yaml > vdr/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 diff --git a/vdr/api/v1/generated.go b/vdr/api/v1/generated.go index 25ddbeb9fc..ca8c80ec94 100644 --- a/vdr/api/v1/generated.go +++ b/vdr/api/v1/generated.go @@ -21,7 +21,7 @@ import ( // DIDResolutionResult defines model for DIDResolutionResult. type DIDResolutionResult struct { - // The actual DID Document. + // The actual DID Document according to the w3c spec. Document DIDDocument `json:"document"` // The DID Document metadata. @@ -34,7 +34,7 @@ type DIDUpdateRequest struct { // hex encoded hash CurrentHash string `json:"currentHash"` - // The actual DID Document. + // The actual DID Document according to the w3c spec. Document DIDDocument `json:"document"` } From 5a60187173875ad3f71c2e1093c888cc6703b2be Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Thu, 21 Jan 2021 16:57:52 +0100 Subject: [PATCH 39/54] added test http handler to test package added tests for vdr engine --- test/http/handler.go | 46 ++++++++++ vdr/api/v1/client.go | 4 +- vdr/api/v1/client_test.go | 38 ++++----- vdr/engine/engine.go | 20 ++--- vdr/engine/engine_test.go | 168 ++++++++++++++++++++++++++++++++++++- vdr/test/diddocument.json | 19 +++++ vdr/test/syntax_error.json | 1 + 7 files changed, 251 insertions(+), 45 deletions(-) create mode 100644 test/http/handler.go create mode 100644 vdr/test/diddocument.json create mode 100644 vdr/test/syntax_error.json 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/client.go b/vdr/api/v1/client.go index 2f4f343235..a70ac689fa 100644 --- a/vdr/api/v1/client.go +++ b/vdr/api/v1/client.go @@ -65,11 +65,11 @@ func (hb HTTPClient) Create() (*did.Document, error) { } } -func (hb HTTPClient) Get(DID did.DID) (*DIDDocument, *DIDDocumentMetadata, error) { +func (hb HTTPClient) Get(DID string) (*DIDDocument, *DIDDocumentMetadata, error) { ctx, cancel := hb.withTimeout() defer cancel() - response, err := hb.client().GetDID(ctx, DID.String()) + response, err := hb.client().GetDID(ctx, DID) if err != nil { return nil, nil, err } diff --git a/vdr/api/v1/client_test.go b/vdr/api/v1/client_test.go index 0eb756e020..a58dbe2050 100644 --- a/vdr/api/v1/client_test.go +++ b/vdr/api/v1/client_test.go @@ -16,28 +16,17 @@ package v1 import ( - "encoding/json" "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" ) -type handler struct { - statusCode int - responseData interface{} -} - -func (h handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { - writer.WriteHeader(h.statusCode) - bytes, _ := json.Marshal(h.responseData) - writer.Write(bytes) -} - func TestHTTPClient_Create(t *testing.T) { did, _ := did2.ParseDID("did:nuts:1") didDoc := did2.Document{ @@ -45,7 +34,7 @@ func TestHTTPClient_Create(t *testing.T) { } t.Run("ok", func(t *testing.T) { - s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: didDoc}) + 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) { @@ -55,7 +44,7 @@ func TestHTTPClient_Create(t *testing.T) { }) t.Run("error - other", func(t *testing.T) { - s := httptest.NewServer(handler{statusCode: http.StatusInternalServerError, responseData: ""}) + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusInternalServerError, ResponseData: ""}) c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} _, err := c.Create() assert.Error(t, err) @@ -63,7 +52,8 @@ func TestHTTPClient_Create(t *testing.T) { } func TestHttpClient_Get(t *testing.T) { - did, _ := did2.ParseDID("did:nuts:1") + didString := "did:nuts:1" + did, _ := did2.ParseDID(didString) didDoc := did2.Document{ ID: *did, } @@ -74,9 +64,9 @@ func TestHttpClient_Get(t *testing.T) { Document: didDoc, DocumentMetadata: meta, } - s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: resolutionResult}) + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusOK, ResponseData: resolutionResult}) c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} - doc, meta, err := c.Get(*did) + doc, meta, err := c.Get(didString) if !assert.NoError(t, err) { return } @@ -85,19 +75,19 @@ func TestHttpClient_Get(t *testing.T) { }) t.Run("error - not found", func(t *testing.T) { - s := httptest.NewServer(handler{statusCode: http.StatusNotFound, responseData: ""}) + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusNotFound, ResponseData: ""}) c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} - _, _, err := c.Get(*did) + _, _, err := c.Get(didString) assert.Error(t, err) }) t.Run("error - invalid response", func(t *testing.T) { - s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: "}"}) + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusOK, ResponseData: "}"}) c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} - _, _, err := c.Get(*did) + _, _, err := c.Get(didString) assert.Error(t, err) }) @@ -112,7 +102,7 @@ func TestHTTPClient_Update(t *testing.T) { hash := "0000000000000000000000000000000000000000" t.Run("ok", func(t *testing.T) { - s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: didDoc}) + 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) { @@ -122,7 +112,7 @@ func TestHTTPClient_Update(t *testing.T) { }) t.Run("error - not found", func(t *testing.T) { - s := httptest.NewServer(handler{statusCode: http.StatusNotFound, responseData: ""}) + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusNotFound, ResponseData: ""}) c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} _, err := c.Update(didString, hash, didDoc) @@ -131,7 +121,7 @@ func TestHTTPClient_Update(t *testing.T) { }) t.Run("error - invalid response", func(t *testing.T) { - s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: "}"}) + s := httptest.NewServer(http2.Handler{StatusCode: http.StatusOK, ResponseData: "}"}) c := HTTPClient{ServerAddress: s.URL, Timeout: time.Second} _, err := c.Update(didString, hash, didDoc) diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go index 1db11c444d..1a6890f3b4 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -90,10 +90,7 @@ func cmd() *cobra.Command { return fmt.Errorf("unable to create new DID: %v", err) } - bytes, err := json.MarshalIndent(doc, "", " ") - if err != nil { - return fmt.Errorf("failed to display created DID document: %v", err) - } + bytes, _ := json.MarshalIndent(doc, "", " ") cmd.Printf("Created DID document: %v\n", string(bytes)) return nil @@ -107,22 +104,14 @@ func cmd() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { client := httpClient() - did, err := did.ParseDID(args[0]) - if err != nil { - return fmt.Errorf("failed to parse DID: %v\n", err) - } - - doc, meta, err := client.Get(*did) + doc, meta, err := client.Get(args[0]) if err != nil { return fmt.Errorf("failed to resolve DID document: %v\n", err) } for _, o := range []interface{}{doc, meta} { - bytes, err := json.MarshalIndent(o, "", " ") - if err != nil { - return fmt.Errorf("failed to display object: %v\n", err) - } - fmt.Printf("%s\n", string(bytes)) + bytes, _ := json.MarshalIndent(o, "", " ") + cmd.Printf("%s\n", string(bytes)) } return nil @@ -166,6 +155,7 @@ func cmd() *cobra.Command { return fmt.Errorf("failed to update DID document: %s\n", err) } + cmd.Println("DID document updated") return nil }, }) diff --git a/vdr/engine/engine_test.go b/vdr/engine/engine_test.go index 9afc14ff0b..1370d6ef64 100644 --- a/vdr/engine/engine_test.go +++ b/vdr/engine/engine_test.go @@ -19,17 +19,21 @@ package engine import ( + "bytes" + "net/http" + "net/http/httptest" + "os" "testing" + "github.com/nuts-foundation/go-did" + http2 "github.com/nuts-foundation/nuts-node/test/http" + v1 "github.com/nuts-foundation/nuts-node/vdr/api/v1" + "github.com/spf13/cobra" "github.com/stretchr/testify/assert" core "github.com/nuts-foundation/nuts-node/core" ) -func TestSearchOrg(t *testing.T) { - // Register test instance singleton -} - func Test_flagSet(t *testing.T) { assert.NotNil(t, flagSet()) } @@ -47,3 +51,159 @@ func TestNewRegistryEngine(t *testing.T) { 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 NewRegistryEngine().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") + }) + }) +} 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 From 0e258f9a282280989f3b1af3980bdccf5ac1d5a4 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Fri, 22 Jan 2021 08:52:59 +0100 Subject: [PATCH 40/54] linter stuff --- crypto/interface.go | 1 + vdr/api/v1/api.go | 3 ++ vdr/api/v1/client.go | 41 ++++++++++++-------- vdr/doc-creator.go | 1 + vdr/engine/engine.go | 81 ++++++++++++++++++++++----------------- vdr/network/ambassador.go | 1 + vdr/types/common.go | 5 ++- vdr/types/interface.go | 2 +- vdr/vdr.go | 6 +++ 9 files changed, 86 insertions(+), 55 deletions(-) diff --git a/crypto/interface.go b/crypto/interface.go index 611b26daf5..02bc84fd49 100644 --- a/crypto/interface.go +++ b/crypto/interface.go @@ -25,6 +25,7 @@ import ( // KidNamingFunc is a function passed to New() which generates the kid for the pub/priv key type KidNamingFunc func(key crypto.PublicKey) (string, error) +// 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 diff --git a/vdr/api/v1/api.go b/vdr/api/v1/api.go index 00fd07c7f8..6f33f17955 100644 --- a/vdr/api/v1/api.go +++ b/vdr/api/v1/api.go @@ -35,6 +35,7 @@ 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 @@ -46,6 +47,7 @@ func (a Wrapper) CreateDID(ctx echo.Context) error { 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 { @@ -69,6 +71,7 @@ func (a Wrapper) GetDID(ctx echo.Context, did string) error { 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 { diff --git a/vdr/api/v1/client.go b/vdr/api/v1/client.go index a70ac689fa..2de9a7e6c2 100644 --- a/vdr/api/v1/client.go +++ b/vdr/api/v1/client.go @@ -52,6 +52,7 @@ func (hb HTTPClient) client() ClientInterface { 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() @@ -65,6 +66,7 @@ func (hb HTTPClient) Create() (*did.Document, error) { } } +// 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() @@ -84,12 +86,13 @@ func (hb HTTPClient) Get(DID string) (*DIDDocument, *DIDDocumentMetadata, error) } } +// 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, + Document: next, CurrentHash: current, } response, err := hb.client().UpdateDID(ctx, DID, requestBody) @@ -98,9 +101,9 @@ func (hb HTTPClient) Update(DID string, current string, next did.Document) (*did } if err := testResponseCode(http.StatusOK, response); err != nil { return nil, err - } else { - return readDIDDocument(response.Body) } + + return readDIDDocument(response.Body) } func (hb HTTPClient) withTimeout() (context.Context, context.CancelFunc) { @@ -117,25 +120,29 @@ func testResponseCode(expectedStatusCode int, response *http.Response) error { } func readDIDDocument(reader io.Reader) (*did.Document, error) { - if data, err := ioutil.ReadAll(reader); err != nil { + 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) - } else { - 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 } + 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) { - if data, err := ioutil.ReadAll(reader); err != nil { + 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) - } else { - 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 } + 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/doc-creator.go b/vdr/doc-creator.go index 4c31f6deb1..7d88ebcd6c 100644 --- a/vdr/doc-creator.go +++ b/vdr/doc-creator.go @@ -18,6 +18,7 @@ import ( 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. diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go index 1a6890f3b4..5203e9fc0a 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -78,7 +78,17 @@ func cmd() *cobra.Command { Short: "Verifiable Data Registry commands", } - cmd.AddCommand(&cobra.Command{ + 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), @@ -95,34 +105,15 @@ func cmd() *cobra.Command { cmd.Printf("Created DID document: %v\n", string(bytes)) return nil }, - }) - - cmd.AddCommand(&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\n", err) - } - - for _, o := range []interface{}{doc, meta} { - bytes, _ := json.MarshalIndent(o, "", " ") - cmd.Printf("%s\n", string(bytes)) - } - - return nil - }, - }) + } +} - cmd.AddCommand(&cobra.Command{ - Use: "update [DID] [hash] [file]", +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), + Args: cobra.RangeArgs(2, 3), RunE: func(cmd *cobra.Command, args []string) error { client := httpClient() @@ -135,32 +126,53 @@ func cmd() *cobra.Command { // read from file bytes, err = ioutil.ReadFile(args[2]) if err != nil { - return fmt.Errorf("failed to read file %s: %s\n", args[2], err) + return fmt.Errorf("failed to read file %s: %s", args[2], err) } } else { // read from stdin bytes, err = readFromStdin() if err != nil { - return fmt.Errorf("failed to read from pipe: %s\n", err) + return fmt.Errorf("failed to read from pipe: %s", err) } } // parse var didDoc did.Document if err = json.Unmarshal(bytes, &didDoc); err != nil { - return fmt.Errorf("failed to parse DID document: %s\n", err) + return fmt.Errorf("failed to parse DID document: %s", err) } if _, err = client.Update(d, h, didDoc); err != nil { - return fmt.Errorf("failed to update DID document: %s\n", err) + return fmt.Errorf("failed to update DID document: %s", err) } cmd.Println("DID document updated") return nil }, - }) + } +} - return cmd +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) { @@ -168,11 +180,10 @@ func readFromStdin() ([]byte, error) { if err != nil { return nil, err } - if fi.Mode() & os.ModeNamedPipe == 0 { + if fi.Mode()&os.ModeNamedPipe == 0 { return nil, errors.New("expected piped input") - } else { - return ioutil.ReadAll(bufio.NewReader(os.Stdin)) } + return ioutil.ReadAll(bufio.NewReader(os.Stdin)) } func httpClient() api.HTTPClient { diff --git a/vdr/network/ambassador.go b/vdr/network/ambassador.go index 994f252c0d..f92259400a 100644 --- a/vdr/network/ambassador.go +++ b/vdr/network/ambassador.go @@ -16,6 +16,7 @@ * along with this program. If not, see . * */ + package network import ( diff --git a/vdr/types/common.go b/vdr/types/common.go index cc1302099b..b83ffc2d44 100644 --- a/vdr/types/common.go +++ b/vdr/types/common.go @@ -22,9 +22,10 @@ import ( "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.0 +// 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. @@ -33,7 +34,7 @@ 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 +// 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 diff --git a/vdr/types/interface.go b/vdr/types/interface.go index b395093706..6a077a787d 100644 --- a/vdr/types/interface.go +++ b/vdr/types/interface.go @@ -29,7 +29,7 @@ type DocResolver interface { Resolve(DID did.DID, metadata *ResolveMetaData) (*did.Document, *DocumentMetadata, error) } -// Create creates a new DID document and returns it. +// DocCreator 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 diff --git a/vdr/vdr.go b/vdr/vdr.go index cbea677bb5..76f5ddd202 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -95,6 +95,7 @@ func RegistryInstance() *Registry { return instance } +// NewRegistryInstance creates a new instance func NewRegistryInstance(config Config, cryptoClient crypto.KeyStore, networkClient pkg.NetworkClient) *Registry { return &Registry{ Config: config, @@ -143,6 +144,7 @@ func (r *Registry) Shutdown() error { return nil } +// Diagnostics returns the diagnostics for this engine func (r *Registry) Diagnostics() []core.DiagnosticResult { return []core.DiagnosticResult{} } @@ -151,6 +153,7 @@ func (r *Registry) getEventsDir() string { return r.Config.Datadir + "/events" } +// Create generates a new DID Document func (r Registry) Create() (*did.Document, error) { doc, err := r.didDocCreator.Create() if err != nil { @@ -169,14 +172,17 @@ func (r Registry) Create() (*did.Document, error) { return doc, nil } +// Resolve resolves a DID Document based on the DID. func (r Registry) Resolve(dID did.DID, metadata *types.ResolveMetaData) (*did.Document, *types.DocumentMetadata, error) { return r.store.Resolve(dID, metadata) } +// Update updates a DID Document based on the DID and current hash func (r Registry) Update(dID did.DID, current hash.SHA256Hash, next did.Document, metadata *types.DocumentMetadata) error { return r.store.Update(dID, current, next, metadata) } +// Deactivate updates the DID Document so it can no longer be updated func (r *Registry) Deactivate(DID did.DID, current hash.SHA256Hash) { panic("implement me") } From 1586d5b439415ae8f717402d6b01aa45b6e05806 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Fri, 22 Jan 2021 08:56:44 +0100 Subject: [PATCH 41/54] one more linter thingy --- vdr/api/v1/client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vdr/api/v1/client.go b/vdr/api/v1/client.go index 2de9a7e6c2..5d94c85620 100644 --- a/vdr/api/v1/client.go +++ b/vdr/api/v1/client.go @@ -79,11 +79,11 @@ func (hb HTTPClient) Get(DID string) (*DIDDocument, *DIDDocumentMetadata, error) return nil, nil, err } - if resolutionResult, err := readDIDResolutionResult(response.Body); err != nil { + var resolutionResult *DIDResolutionResult + if resolutionResult, err = readDIDResolutionResult(response.Body); err != nil { return nil, nil, err - } else { - return &resolutionResult.Document, &resolutionResult.DocumentMetadata, nil } + return &resolutionResult.Document, &resolutionResult.DocumentMetadata, nil } // Update a DID Document given a DID and its current hash. From 424bb13d5b287030ea5ccffb8d7bc077706fc3a6 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Fri, 22 Jan 2021 09:09:31 +0100 Subject: [PATCH 42/54] Added some test coverage Removed obsolete config from vdr --- crypto/crypto_test.go | 8 ++++++++ crypto/hash/hash_test.go | 22 ++++++++++++++++++++++ vdr/config.go | 22 ++-------------------- vdr/engine/engine.go | 5 +---- vdr/vdr.go | 8 +++----- 5 files changed, 36 insertions(+), 29 deletions(-) diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 4f857ca471..629313141d 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -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/hash/hash_test.go b/crypto/hash/hash_test.go index bc75df749e..44f27be8e0 100644 --- a/crypto/hash/hash_test.go +++ b/crypto/hash/hash_test.go @@ -101,6 +101,21 @@ func TestParseHex(t *testing.T) { }) } +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) @@ -126,6 +141,13 @@ func TestSHA256Hash_UnmarshalJSON(t *testing.T) { } 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) { diff --git a/vdr/config.go b/vdr/config.go index 8826cceb87..a48bd563a7 100644 --- a/vdr/config.go +++ b/vdr/config.go @@ -9,32 +9,15 @@ const ConfMode = "mode" // ConfAddress is the config name for the http server/client address const ConfAddress = "address" -// ConfSyncMode is the config name for the used SyncMode -const ConfSyncMode = "syncMode" - -// ConfSyncAddress is the config name for the remote address used to fetch updated registry files -const ConfSyncAddress = "syncAddress" - -// ConfSyncInterval is the config name for the interval in minutes to look for new registry files online -const ConfSyncInterval = "syncInterval" - -// ConfOrganisationCertificateValidity is the config name for the number of days organisation certificates are valid -const ConfOrganisationCertificateValidity = "organisationCertificateValidity" - -// ConfVendorCACertificateValidity is the config name for the number of days vendor CA certificates are valid -const ConfVendorCACertificateValidity = "vendorCACertificateValidity" - // ConfClientTimeout is the time-out for the client in seconds (e.g. when using the CLI). const ConfClientTimeout = "clientTimeout" -// ModuleName == Registry -const ModuleName = "Registry" +// ModuleName == Verifiable Data Registry +const ModuleName = "Verifiable Data Registry" // Config holds the config for the Registry engine type Config struct { - Mode string Datadir string - Address string ClientTimeout int } @@ -42,7 +25,6 @@ type Config struct { func DefaultRegistryConfig() Config { return Config{ Datadir: "./data", - Address: "localhost:1323", ClientTimeout: 10, } } diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go index 5203e9fc0a..4ec4e4f00d 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -29,7 +29,6 @@ import ( "time" "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/nuts-network/pkg" "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/vdr" api "github.com/nuts-foundation/nuts-node/vdr/api/v1" @@ -50,7 +49,7 @@ func NewRegistryEngine() *core.Engine { Config: &r.Config, ConfigKey: "vdr", FlagSet: flagSet(), - Name: pkg.ModuleName, + Name: vdr.ModuleName, Routes: func(router core.EchoRouter) { api.RegisterHandlers(router, &api.Wrapper{VDR: r}) }, @@ -65,8 +64,6 @@ func flagSet() *pflag.FlagSet { defs := vdr.DefaultRegistryConfig() flagSet.String(vdr.ConfDataDir, defs.Datadir, fmt.Sprintf("Location of data files, default: %s", defs.Datadir)) - flagSet.String(vdr.ConfMode, defs.Mode, fmt.Sprintf("server or client, when client it uses the HTTPClient, default: %s", defs.Mode)) - flagSet.String(vdr.ConfAddress, defs.Address, fmt.Sprintf("Interface and port for http server to bind to, default: %s", defs.Address)) 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 diff --git a/vdr/vdr.go b/vdr/vdr.go index 76f5ddd202..e5f71f2f25 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -113,9 +113,7 @@ func (r *Registry) Configure() error { r.configOnce.Do(func() { cfg := core.NutsConfig() - r.Config.Mode = cfg.GetEngineMode(r.Config.Mode) - if r.Config.Mode == core.ServerEngineMode { - //r.Db = db.New() + if cfg.Mode() == core.ServerEngineMode { if r.networkAmbassador == nil { r.networkAmbassador = network.NewAmbassador(r.network, r.crypto) } @@ -126,7 +124,7 @@ func (r *Registry) Configure() error { // Start initiates the routines for auto-updating the data func (r *Registry) Start() error { - if r.Config.Mode == core.ServerEngineMode { + if core.NutsConfig().Mode() == core.ServerEngineMode { r.networkAmbassador.Start() } return nil @@ -134,7 +132,7 @@ func (r *Registry) Start() error { // Shutdown cleans up any leftover go routines func (r *Registry) Shutdown() error { - if r.Config.Mode == core.ServerEngineMode { + if core.NutsConfig().Mode() == core.ServerEngineMode { logging.Log().Debug("Sending close signal to all routines") for _, ch := range r.closers { ch <- struct{}{} From c0ab6686fc597cd2000b1260bc5da35ba3bfde3c Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Fri, 22 Jan 2021 09:23:55 +0100 Subject: [PATCH 43/54] coverage for vdr api client --- vdr/api/v1/client.go | 4 ---- vdr/api/v1/client_test.go | 41 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/vdr/api/v1/client.go b/vdr/api/v1/client.go index 5d94c85620..e26c070939 100644 --- a/vdr/api/v1/client.go +++ b/vdr/api/v1/client.go @@ -29,7 +29,6 @@ import ( "io/ioutil" "net/http" - "strings" "time" ) @@ -41,9 +40,6 @@ type HTTPClient struct { func (hb HTTPClient) client() ClientInterface { url := hb.ServerAddress - if !strings.Contains(url, "http") { - url = fmt.Sprintf("http://%v", hb.ServerAddress) - } response, err := NewClientWithResponses(url) if err != nil { diff --git a/vdr/api/v1/client_test.go b/vdr/api/v1/client_test.go index a58dbe2050..e148274c16 100644 --- a/vdr/api/v1/client_test.go +++ b/vdr/api/v1/client_test.go @@ -16,6 +16,7 @@ package v1 import ( + "errors" "net/http" "net/http/httptest" "testing" @@ -43,12 +44,18 @@ func TestHTTPClient_Create(t *testing.T) { assert.NotNil(t, doc) }) - t.Run("error - other", func(t *testing.T) { + 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) { @@ -91,6 +98,12 @@ func TestHttpClient_Get(t *testing.T) { 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) { @@ -128,4 +141,30 @@ func TestHTTPClient_Update(t *testing.T) { 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!") } From 8ca225d95ee3eaf197d0db45cdeee3a82bb876ad Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Fri, 22 Jan 2021 09:26:07 +0100 Subject: [PATCH 44/54] Rename Registry to VDR --- cmd/root.go | 2 +- docs/pages/configuration/options.rst | 2 +- vdr/config.go | 6 ++-- vdr/engine/engine.go | 12 +++---- vdr/engine/engine_test.go | 6 ++-- vdr/vdr.go | 47 +++++++++++----------------- 6 files changed, 33 insertions(+), 42 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 1ec4c0b1ae..bedd0102b4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -105,7 +105,7 @@ func registerEngines() { core.RegisterEngine(core.NewLoggerEngine()) core.RegisterEngine(core.NewMetricsEngine()) core.RegisterEngine(crypto.NewCryptoEngine()) - core.RegisterEngine(vdr.NewRegistryEngine()) + core.RegisterEngine(vdr.NewVDREngine()) } diff --git a/docs/pages/configuration/options.rst b/docs/pages/configuration/options.rst index 8aae0df778..4f8eef7e01 100755 --- a/docs/pages/configuration/options.rst +++ b/docs/pages/configuration/options.rst @@ -45,7 +45,7 @@ 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** +**VDR** 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 diff --git a/vdr/config.go b/vdr/config.go index 8826cceb87..571a953e00 100644 --- a/vdr/config.go +++ b/vdr/config.go @@ -27,10 +27,10 @@ const ConfVendorCACertificateValidity = "vendorCACertificateValidity" // ConfClientTimeout is the time-out for the client in seconds (e.g. when using the CLI). const ConfClientTimeout = "clientTimeout" -// ModuleName == Registry -const ModuleName = "Registry" +// ModuleName == VDR +const ModuleName = "VDR" -// Config holds the config for the Registry engine +// Config holds the config for the VDR engine type Config struct { Mode string Datadir string diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go index 1a6890f3b4..a86d1d92bb 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -40,8 +40,8 @@ import ( // registryClientCreator is a variable to aid testability var registryClientCreator = vdr.RegistryInstance() -// NewRegistryEngine returns the core definition for the registry -func NewRegistryEngine() *core.Engine { +// NewVDREngine returns the core definition for the registry +func NewVDREngine() *core.Engine { r := vdr.RegistryInstance() return &core.Engine{ @@ -75,7 +75,7 @@ func flagSet() *pflag.FlagSet { func cmd() *cobra.Command { cmd := &cobra.Command{ Use: "vdr", - Short: "Verifiable Data Registry commands", + Short: "Verifiable Data VDR commands", } cmd.AddCommand(&cobra.Command{ @@ -119,10 +119,10 @@ func cmd() *cobra.Command { }) cmd.AddCommand(&cobra.Command{ - Use: "update [DID] [hash] [file]", + 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), + Args: cobra.RangeArgs(2, 3), RunE: func(cmd *cobra.Command, args []string) error { client := httpClient() @@ -168,7 +168,7 @@ func readFromStdin() ([]byte, error) { if err != nil { return nil, err } - if fi.Mode() & os.ModeNamedPipe == 0 { + if fi.Mode()&os.ModeNamedPipe == 0 { return nil, errors.New("expected piped input") } else { return ioutil.ReadAll(bufio.NewReader(os.Stdin)) diff --git a/vdr/engine/engine_test.go b/vdr/engine/engine_test.go index 1370d6ef64..90aac166ac 100644 --- a/vdr/engine/engine_test.go +++ b/vdr/engine/engine_test.go @@ -41,11 +41,11 @@ func Test_flagSet(t *testing.T) { func TestNewRegistryEngine(t *testing.T) { // Register test instance singleton t.Run("instance", func(t *testing.T) { - assert.NotNil(t, NewRegistryEngine()) + assert.NotNil(t, NewVDREngine()) }) t.Run("configuration", func(t *testing.T) { - e := NewRegistryEngine() + e := NewVDREngine() cfg := core.NutsConfig() cfg.RegisterFlags(e.Cmd, e) assert.NoError(t, cfg.InjectIntoEngine(e)) @@ -56,7 +56,7 @@ func TestEngine_Command(t *testing.T) { core.NutsConfig().Load(&cobra.Command{}) createCmd := func(t *testing.T) *cobra.Command { - return NewRegistryEngine().Cmd + return NewVDREngine().Cmd } exampleID, _ := did.ParseDID("did:nuts:Fx8kamg7Bom4gyEzmJc9t9QmWTkCwSxu3mrp3CbkehR7") diff --git a/vdr/vdr.go b/vdr/vdr.go index cbea677bb5..fa9a0f15b1 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -48,19 +48,11 @@ import ( "github.com/nuts-foundation/nuts-node/crypto/hash" ) -//type StoreWrapper struct { -// networkClient networkPkg.NetworkClient -// store DIDStore -//} -// -//func wrap(store DIDStore) DIDStore { -// return &StoreWrapper(store: store) -//} - -// Registry holds the config and Db reference -type Registry struct { - Config Config - //Db db.Db +// 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 an engine which can be started providing an http API server and client and a +type VDR struct { + Config Config store types.Store network networkPkg.NetworkClient crypto crypto.KeyStore @@ -72,7 +64,7 @@ type Registry struct { didDocCreator types.DocCreator } -var instance *Registry +var instance *VDR var oneRegistry sync.Once // ReloadRegistryIdleTimeout defines the cooling down period after receiving a file watcher notification, before @@ -83,8 +75,8 @@ func init() { ReloadRegistryIdleTimeout = 3 * time.Second } -// RegistryInstance returns the singleton Registry -func RegistryInstance() *Registry { +// RegistryInstance returns the singleton VDR +func RegistryInstance() *VDR { if instance != nil { return instance } @@ -95,8 +87,8 @@ func RegistryInstance() *Registry { return instance } -func NewRegistryInstance(config Config, cryptoClient crypto.KeyStore, networkClient pkg.NetworkClient) *Registry { - return &Registry{ +func NewRegistryInstance(config Config, cryptoClient crypto.KeyStore, networkClient pkg.NetworkClient) *VDR { + return &VDR{ Config: config, crypto: cryptoClient, network: networkClient, @@ -107,14 +99,13 @@ func NewRegistryInstance(config Config, cryptoClient crypto.KeyStore, networkCli } // Configure initializes the db, but only when in server mode -func (r *Registry) Configure() error { +func (r *VDR) Configure() error { var err error r.configOnce.Do(func() { cfg := core.NutsConfig() r.Config.Mode = cfg.GetEngineMode(r.Config.Mode) if r.Config.Mode == core.ServerEngineMode { - //r.Db = db.New() if r.networkAmbassador == nil { r.networkAmbassador = network.NewAmbassador(r.network, r.crypto) } @@ -124,7 +115,7 @@ func (r *Registry) Configure() error { } // Start initiates the routines for auto-updating the data -func (r *Registry) Start() error { +func (r *VDR) Start() error { if r.Config.Mode == core.ServerEngineMode { r.networkAmbassador.Start() } @@ -132,7 +123,7 @@ func (r *Registry) Start() error { } // Shutdown cleans up any leftover go routines -func (r *Registry) Shutdown() error { +func (r *VDR) Shutdown() error { if r.Config.Mode == core.ServerEngineMode { logging.Log().Debug("Sending close signal to all routines") for _, ch := range r.closers { @@ -143,15 +134,15 @@ func (r *Registry) Shutdown() error { return nil } -func (r *Registry) Diagnostics() []core.DiagnosticResult { +func (r *VDR) Diagnostics() []core.DiagnosticResult { return []core.DiagnosticResult{} } -func (r *Registry) getEventsDir() string { +func (r *VDR) getEventsDir() string { return r.Config.Datadir + "/events" } -func (r Registry) Create() (*did.Document, error) { +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) @@ -169,14 +160,14 @@ func (r Registry) Create() (*did.Document, error) { return doc, nil } -func (r Registry) Resolve(dID did.DID, metadata *types.ResolveMetaData) (*did.Document, *types.DocumentMetadata, error) { +func (r VDR) Resolve(dID did.DID, metadata *types.ResolveMetaData) (*did.Document, *types.DocumentMetadata, error) { return r.store.Resolve(dID, metadata) } -func (r Registry) Update(dID did.DID, current hash.SHA256Hash, next did.Document, metadata *types.DocumentMetadata) error { +func (r VDR) Update(dID did.DID, current hash.SHA256Hash, next did.Document, metadata *types.DocumentMetadata) error { return r.store.Update(dID, current, next, metadata) } -func (r *Registry) Deactivate(DID did.DID, current hash.SHA256Hash) { +func (r *VDR) Deactivate(DID did.DID, current hash.SHA256Hash) { panic("implement me") } From 402f40cf5626ed73752ced256cfbee0342506246 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Fri, 22 Jan 2021 09:27:11 +0100 Subject: [PATCH 45/54] Add interfaces to engine Makes it easyer to implement engine methods --- cmd/root.go | 6 +++--- core/diagnostics.go | 4 ++++ core/engine.go | 27 ++++++++++++++++----------- core/logging.go | 15 +++++++++++---- core/metrics.go | 6 ++++-- core/status.go | 12 ++++++------ crypto/crypto.go | 4 ++++ crypto/engine/engine.go | 4 ++-- vdr/engine/engine.go | 17 ++++++++--------- 9 files changed, 58 insertions(+), 37 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index bedd0102b4..ba2fa34b27 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -121,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) } @@ -137,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) } @@ -147,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/core/diagnostics.go b/core/diagnostics.go index 73dfd4bdff..b00e1a2da4 100644 --- a/core/diagnostics.go +++ b/core/diagnostics.go @@ -19,6 +19,10 @@ package core +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/engine.go b/core/engine.go index ec23261054..fdbc908b18 100644 --- a/core/engine.go +++ b/core/engine.go @@ -50,6 +50,19 @@ 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. +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. +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 +83,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/logging.go b/core/logging.go index 526cea71a5..d9a3db3e02 100644 --- a/core/logging.go +++ b/core/logging.go @@ -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 +} + +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 0f1ec359a7..54f422516c 100644 --- a/core/metrics.go +++ b/core/metrics.go @@ -32,14 +32,16 @@ const NutsMetricsPrefix = "nuts_" func NewMetricsEngine() *Engine { return &Engine{ Name: "Metrics", - Configure: configure, + Configurable: metricsEngine{}, Routes: func(router EchoRouter) { router.GET("/metrics", echo.WrapHandler(promhttp.Handler())) }, } } -func configure() error { +type metricsEngine struct {} + +func (metricsEngine) Configure() error { collectors := []prometheus.Collector{ prometheus.NewGoCollector(), prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}), diff --git a/core/status.go b/core/status.go index ac2e9fd951..a3cbd029d1 100644 --- a/core/status.go +++ b/core/status.go @@ -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) @@ -53,10 +51,11 @@ func diagnosticsOverview(ctx echo.Context) error { return ctx.String(http.StatusOK, diagnosticsSummaryAsText()) } + 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 +67,9 @@ func diagnosticsSummaryAsText() string { return strings.Join(lines, "\n") } -func diagnostics() DiagnosticResult { - return &GenericDiagnosticResult{Title: "Registered engines", Outcome: strings.Join(listAllEngines(), ",")} +type status struct {} +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/crypto.go b/crypto/crypto.go index cdcf6b5290..9d52224919 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -82,6 +82,10 @@ func (client *Crypto) Shutdown() error { return nil } +func (client *Crypto) Start() error { + 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) diff --git a/crypto/engine/engine.go b/crypto/engine/engine.go index f260c2e227..481aeed263 100644 --- a/crypto/engine/engine.go +++ b/crypto/engine/engine.go @@ -47,13 +47,13 @@ func NewCryptoEngine() *core.Engine { Cmd: cmd(), Config: &cb.Config, ConfigKey: "crypto", - Configure: cb.Configure, + 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/vdr/engine/engine.go b/vdr/engine/engine.go index a86d1d92bb..7275e18afb 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -45,18 +45,17 @@ func NewVDREngine() *core.Engine { r := vdr.RegistryInstance() return &core.Engine{ - Cmd: cmd(), - Configure: r.Configure, - Config: &r.Config, - ConfigKey: "vdr", - FlagSet: flagSet(), - Name: pkg.ModuleName, + Cmd: cmd(), + Runnable: r, + Configurable: r, + Diagnosable: r, + Config: &r.Config, + ConfigKey: "vdr", + FlagSet: flagSet(), + Name: pkg.ModuleName, Routes: func(router core.EchoRouter) { api.RegisterHandlers(router, &api.Wrapper{VDR: r}) }, - Start: r.Start, - Shutdown: r.Shutdown, - Diagnostics: r.Diagnostics, } } From a015e0ada5fce467fc1fee8b4e794b9ed4d909df Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Fri, 22 Jan 2021 09:27:33 +0100 Subject: [PATCH 46/54] remove unused references to crypto from vdr --- vdr/network/ambassador.go | 6 ++---- vdr/vdr.go | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/vdr/network/ambassador.go b/vdr/network/ambassador.go index 994f252c0d..c95e3ba963 100644 --- a/vdr/network/ambassador.go +++ b/vdr/network/ambassador.go @@ -23,7 +23,7 @@ import ( network "github.com/nuts-foundation/nuts-network/pkg" "github.com/nuts-foundation/nuts-network/pkg/model" - "github.com/nuts-foundation/nuts-node/crypto" + "github.com/nuts-foundation/nuts-node/vdr/logging" ) @@ -38,14 +38,12 @@ type Ambassador interface { type ambassador struct { networkClient network.NetworkClient - cryptoClient crypto.KeyStore } // NewAmbassador creates a new Ambassador. Don't forget to call RegisterEventHandlers afterwards. -func NewAmbassador(networkClient network.NetworkClient, cryptoClient crypto.KeyStore) Ambassador { +func NewAmbassador(networkClient network.NetworkClient) Ambassador { instance := &ambassador{ networkClient: networkClient, - cryptoClient: cryptoClient, } return instance } diff --git a/vdr/vdr.go b/vdr/vdr.go index fa9a0f15b1..7a06034b93 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -55,8 +55,7 @@ type VDR struct { Config Config store types.Store network networkPkg.NetworkClient - crypto crypto.KeyStore - OnChange func(registry *Registry) + OnChange func(registry *VDR) networkAmbassador network.Ambassador configOnce sync.Once _logger *logrus.Entry @@ -90,7 +89,6 @@ func RegistryInstance() *VDR { func NewRegistryInstance(config Config, cryptoClient crypto.KeyStore, networkClient pkg.NetworkClient) *VDR { return &VDR{ Config: config, - crypto: cryptoClient, network: networkClient, _logger: logging.Log(), store: store.NewMemoryStore(), @@ -107,7 +105,7 @@ func (r *VDR) Configure() error { r.Config.Mode = cfg.GetEngineMode(r.Config.Mode) if r.Config.Mode == core.ServerEngineMode { if r.networkAmbassador == nil { - r.networkAmbassador = network.NewAmbassador(r.network, r.crypto) + r.networkAmbassador = network.NewAmbassador(r.network) } } }) From 148bc220ddc42fb6bdf1e6d855476e1bb0c17630 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Fri, 22 Jan 2021 09:27:56 +0100 Subject: [PATCH 47/54] Update .gitignore Ignore pem files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ab8c059e5b..dd6538382c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ data # MacOS .DS_Store +*.pem From d01d558a24b97098be226d09fe92e05c223a8eb2 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Fri, 22 Jan 2021 09:44:09 +0100 Subject: [PATCH 48/54] Fix gofmt and golint errors --- core/diagnostics.go | 3 ++- core/logging.go | 1 - core/metrics.go | 4 ++-- core/status.go | 4 ++-- crypto/api/v1/generated.go | 1 - crypto/crypto.go | 2 ++ crypto/engine/engine.go | 10 +++++----- vdr/api/v1/api_test.go | 8 ++++---- vdr/api/v1/client_test.go | 2 +- vdr/api/v1/generated.go | 1 - vdr/doc-creator_test.go | 2 +- vdr/engine/engine_test.go | 2 +- vdr/vdr.go | 2 +- 13 files changed, 21 insertions(+), 21 deletions(-) diff --git a/core/diagnostics.go b/core/diagnostics.go index b00e1a2da4..eee628d35f 100644 --- a/core/diagnostics.go +++ b/core/diagnostics.go @@ -19,8 +19,9 @@ package core +// Diagnosable allows the implementer, mostly engines, to return diagnostics. type Diagnosable interface { - Diagnostics() [] DiagnosticResult + Diagnostics() []DiagnosticResult } // DiagnosticResult are the result of different checks giving information on how well the system is doing diff --git a/core/logging.go b/core/logging.go index d9a3db3e02..ecbfce3948 100644 --- a/core/logging.go +++ b/core/logging.go @@ -54,4 +54,3 @@ 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 54f422516c..101444ec37 100644 --- a/core/metrics.go +++ b/core/metrics.go @@ -31,7 +31,7 @@ const NutsMetricsPrefix = "nuts_" // Metrics are exposed on /metrics, by default the GoCollector and ProcessCollector are enabled. func NewMetricsEngine() *Engine { return &Engine{ - Name: "Metrics", + Name: "Metrics", Configurable: metricsEngine{}, Routes: func(router EchoRouter) { router.GET("/metrics", echo.WrapHandler(promhttp.Handler())) @@ -39,7 +39,7 @@ func NewMetricsEngine() *Engine { } } -type metricsEngine struct {} +type metricsEngine struct{} func (metricsEngine) Configure() error { collectors := []prometheus.Collector{ diff --git a/core/status.go b/core/status.go index a3cbd029d1..dff0a0a9fc 100644 --- a/core/status.go +++ b/core/status.go @@ -51,7 +51,6 @@ func diagnosticsOverview(ctx echo.Context) error { return ctx.String(http.StatusOK, diagnosticsSummaryAsText()) } - func diagnosticsSummaryAsText() string { var lines []string for _, e := range EngineCtl.Engines { @@ -67,7 +66,8 @@ func diagnosticsSummaryAsText() string { return strings.Join(lines, "\n") } -type status struct {} +type status struct{} + func (status) Diagnostics() []DiagnosticResult { return []DiagnosticResult{&GenericDiagnosticResult{Title: "Registered engines", Outcome: strings.Join(listAllEngines(), ",")}} } diff --git a/crypto/api/v1/generated.go b/crypto/api/v1/generated.go index cee6a0d18a..e39292278a 100644 --- a/crypto/api/v1/generated.go +++ b/crypto/api/v1/generated.go @@ -463,4 +463,3 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/internal/crypto/v1/sign_jwt", wrapper.SignJwt) } - diff --git a/crypto/crypto.go b/crypto/crypto.go index 9d52224919..33f23dd38c 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -82,7 +82,9 @@ 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 } diff --git a/crypto/engine/engine.go b/crypto/engine/engine.go index 481aeed263..25823282e0 100644 --- a/crypto/engine/engine.go +++ b/crypto/engine/engine.go @@ -44,12 +44,12 @@ func NewCryptoEngine() *core.Engine { cb := crypto2.Instance() return &core.Engine{ - Cmd: cmd(), - Config: &cb.Config, - ConfigKey: "crypto", + Cmd: cmd(), + Config: &cb.Config, + ConfigKey: "crypto", Configurable: cb, - FlagSet: flagSet(), - Name: "Crypto", + FlagSet: flagSet(), + Name: "Crypto", Routes: func(router core.EchoRouter) { api.RegisterHandlers(router, &api.Wrapper{C: cb}) }, diff --git a/vdr/api/v1/api_test.go b/vdr/api/v1/api_test.go index 782dc6b514..27dd13cdcd 100644 --- a/vdr/api/v1/api_test.go +++ b/vdr/api/v1/api_test.go @@ -126,8 +126,8 @@ func TestWrapper_UpdateDID(t *testing.T) { ID: *did, } didUpdate := DIDUpdateRequest{ - Document: *didDoc, - CurrentHash: "452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620", + Document: *didDoc, + CurrentHash: "452d9e89d5bd5d9225fb6daecd579e7388a166c7661ca04e47fd3cd8446e4620", } t.Run("ok", func(t *testing.T) { @@ -200,8 +200,8 @@ func TestWrapper_UpdateDID(t *testing.T) { defer ctx.ctrl.Finish() didUpdate := DIDUpdateRequest{ - Document: *didDoc, - CurrentHash: "0", + Document: *didDoc, + CurrentHash: "0", } ctx.echo.EXPECT().Bind(gomock.Any()).DoAndReturn(func(f interface{}) error { diff --git a/vdr/api/v1/client_test.go b/vdr/api/v1/client_test.go index e148274c16..8b22c659c2 100644 --- a/vdr/api/v1/client_test.go +++ b/vdr/api/v1/client_test.go @@ -163,7 +163,7 @@ func TestReadDIDResolutionResult(t *testing.T) { }) } -type errReader struct {} +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 index ca8c80ec94..b3a925a57c 100644 --- a/vdr/api/v1/generated.go +++ b/vdr/api/v1/generated.go @@ -584,4 +584,3 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.PUT(baseURL+"/internal/vdr/v1/did/:did", wrapper.UpdateDID) } - diff --git a/vdr/doc-creator_test.go b/vdr/doc-creator_test.go index 27e50e7edc..113ca41bbf 100644 --- a/vdr/doc-creator_test.go +++ b/vdr/doc-creator_test.go @@ -59,7 +59,7 @@ func TestDocCreator_Create(t *testing.T) { 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){ + if assert.NoError(t, err) { return } diff --git a/vdr/engine/engine_test.go b/vdr/engine/engine_test.go index 90aac166ac..2cc3bdd3c8 100644 --- a/vdr/engine/engine_test.go +++ b/vdr/engine/engine_test.go @@ -61,7 +61,7 @@ func TestEngine_Command(t *testing.T) { exampleID, _ := did.ParseDID("did:nuts:Fx8kamg7Bom4gyEzmJc9t9QmWTkCwSxu3mrp3CbkehR7") exampleDIDDocument := did.Document{ - ID: *exampleID, + ID: *exampleID, Controller: []did.DID{*exampleID}, } diff --git a/vdr/vdr.go b/vdr/vdr.go index 80326b8208..efb40c1ef3 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -88,7 +88,7 @@ func RegistryInstance() *VDR { // NewRegistryInstance creates a new instance func NewRegistryInstance(config Config, cryptoClient crypto.KeyStore, networkClient pkg.NetworkClient) *VDR { - return &VDR{ + return &VDR{ Config: config, network: networkClient, _logger: logging.Log(), From 4bc6327345f063a992cd398d4455cc15e61a68d7 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Fri, 22 Jan 2021 09:50:29 +0100 Subject: [PATCH 49/54] unused consts --- vdr/config.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/vdr/config.go b/vdr/config.go index bc2f4cd9dd..a60973ae79 100644 --- a/vdr/config.go +++ b/vdr/config.go @@ -3,12 +3,6 @@ package vdr // ConfDataDir is the config name for specifiying the data location of the requiredFiles const ConfDataDir = "datadir" -// ConfMode is the config name for the engine mode, server or client -const ConfMode = "mode" - -// ConfAddress is the config name for the http server/client address -const ConfAddress = "address" - // ConfClientTimeout is the time-out for the client in seconds (e.g. when using the CLI). const ConfClientTimeout = "clientTimeout" From 5da542b19157ff1f51470e2957aeae913025dd5e Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Fri, 22 Jan 2021 12:39:32 +0100 Subject: [PATCH 50/54] Fix PR feedback Renamed everything from Registry to VDR Fixed comments and docs Added more test coverage --- core/engine.go | 2 ++ core/logging.go | 1 + core/metrics.go | 4 +-- core/status.go | 2 ++ vdr/config.go | 6 ++-- vdr/doc-creator.go | 5 ++++ vdr/doc-creator_test.go | 61 +++++++++++++++++++++++++++++++++++------ vdr/engine/engine.go | 7 ++--- vdr/logging/log.go | 2 +- vdr/logging/log_test.go | 2 +- vdr/types/common.go | 4 +++ vdr/types/interface.go | 11 ++++---- vdr/vdr.go | 24 ++++++---------- 13 files changed, 90 insertions(+), 41 deletions(-) diff --git a/core/engine.go b/core/engine.go index fdbc908b18..041a3cb2e5 100644 --- a/core/engine.go +++ b/core/engine.go @@ -52,6 +52,7 @@ type EchoRouter interface { // 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 @@ -59,6 +60,7 @@ type Runnable interface { // 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 } diff --git a/core/logging.go b/core/logging.go index ecbfce3948..4e4045f0b6 100644 --- a/core/logging.go +++ b/core/logging.go @@ -50,6 +50,7 @@ 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 101444ec37..0ad8c592d3 100644 --- a/core/metrics.go +++ b/core/metrics.go @@ -25,8 +25,6 @@ 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 { @@ -41,6 +39,8 @@ func NewMetricsEngine() *Engine { type metricsEngine struct{} +// Configure configures the MetricsEngine. +// It configures and registers the prometheus collector func (metricsEngine) Configure() error { collectors := []prometheus.Collector{ prometheus.NewGoCollector(), diff --git a/core/status.go b/core/status.go index dff0a0a9fc..53e53475c6 100644 --- a/core/status.go +++ b/core/status.go @@ -68,6 +68,8 @@ func diagnosticsSummaryAsText() string { 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(), ",")}} } diff --git a/vdr/config.go b/vdr/config.go index bc2f4cd9dd..54586e6e05 100644 --- a/vdr/config.go +++ b/vdr/config.go @@ -12,7 +12,7 @@ const ConfAddress = "address" // ConfClientTimeout is the time-out for the client in seconds (e.g. when using the CLI). const ConfClientTimeout = "clientTimeout" -// ModuleName == VDR +// ModuleName contains the name of this module const ModuleName = "Verifiable Data Registry" // Config holds the config for the VDR engine @@ -21,8 +21,8 @@ type Config struct { ClientTimeout int } -// DefaultRegistryConfig returns a fresh Config filled with default values -func DefaultRegistryConfig() Config { +// 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 index 7d88ebcd6c..44edfcb8ff 100644 --- a/vdr/doc-creator.go +++ b/vdr/doc-creator.go @@ -33,6 +33,10 @@ func didKidNamingFunc(pKey crypto.PublicKey) (string, error) { 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: // -------------------- @@ -79,6 +83,7 @@ func (n NutsDocCreator) Create() (*did.Document, error) { doc := &did.Document{ Context: []did.URI{did.DIDContextV1URI()}, ID: *didID, + Controller: []did.DID{*didID}, VerificationMethod: []did.VerificationMethod{*verificationMethod}, Authentication: []did.VerificationRelationship{{VerificationMethod: verificationMethod}}, } diff --git a/vdr/doc-creator_test.go b/vdr/doc-creator_test.go index 113ca41bbf..c32616f7a0 100644 --- a/vdr/doc-creator_test.go +++ b/vdr/doc-creator_test.go @@ -8,6 +8,7 @@ import ( "crypto/rsa" "testing" + "github.com/lestrrat-go/jwx/jwa" "github.com/lestrrat-go/jwx/jwk" "github.com/stretchr/testify/assert" @@ -18,20 +19,15 @@ import ( 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) { - keySet, err := jwk.ParseString(m.jwkStr) + rawKey, err := jwkToPublicKey(m.t, m.jwkStr) if err != nil { return nil, "", err } - key := keySet.Keys[0] - - var rawKey crypto.PublicKey - if key.Raw(&rawKey) != nil { - return nil, "", err - } kid, err := namingFunc(rawKey) if err != nil { return nil, "", err @@ -39,19 +35,32 @@ func (m *mockKeyCreator) New(namingFunc nutsCrypto.KidNamingFunc) (crypto.Public 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{ - `{"crv":"P-256","kid":"did:nuts:ARRW2e42qyVjQZiACk4Up3mzpshZdJBDBPWsuFQPcDiS#J9O6wvqtYOVwjc8JtZ4aodRdbPv_IKAjLkEq9uHlDdE","kty":"EC","x":"Qn6xbZtOYFoLO2qMEAczcau9uGGWwa1bT+7JmAVLtg4=","y":"d20dD0qlT+d1djVpAfrfsAfKOUxKwKkn1zqFSIuJ398="},"type":"JsonWebKey2020"}`, + 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) }) }) } @@ -81,4 +90,40 @@ func Test_didKidNamingFunc(t *testing.T) { 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 index 9276988df8..84532ca6c8 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -36,12 +36,9 @@ import ( "github.com/spf13/pflag" ) -// registryClientCreator is a variable to aid testability -var registryClientCreator = vdr.RegistryInstance() - // NewVDREngine returns the core definition for the registry func NewVDREngine() *core.Engine { - r := vdr.RegistryInstance() + r := vdr.Instance() return &core.Engine{ Cmd: cmd(), @@ -61,7 +58,7 @@ func NewVDREngine() *core.Engine { func flagSet() *pflag.FlagSet { flagSet := pflag.NewFlagSet("registry", pflag.ContinueOnError) - defs := vdr.DefaultRegistryConfig() + 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)) diff --git a/vdr/logging/log.go b/vdr/logging/log.go index 4e3fdc4891..09723c4a39 100644 --- a/vdr/logging/log.go +++ b/vdr/logging/log.go @@ -22,7 +22,7 @@ import ( "github.com/sirupsen/logrus" ) -var _logger = logrus.StandardLogger().WithField("module", "DID Store") +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. diff --git a/vdr/logging/log_test.go b/vdr/logging/log_test.go index 1b40f7041b..bb659af412 100644 --- a/vdr/logging/log_test.go +++ b/vdr/logging/log_test.go @@ -10,6 +10,6 @@ func TestLog(t *testing.T) { Log().Info("Works") }) t.Run("has correct module field", func(t *testing.T) { - assert.Equal(t, Log().Data["module"], "DID Store") + assert.Equal(t, Log().Data["module"], "VDR") }) } diff --git a/vdr/types/common.go b/vdr/types/common.go index b83ffc2d44..48eae548dc 100644 --- a/vdr/types/common.go +++ b/vdr/types/common.go @@ -1,16 +1,20 @@ /* * 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 diff --git a/vdr/types/interface.go b/vdr/types/interface.go index 6a077a787d..54cb779e7b 100644 --- a/vdr/types/interface.go +++ b/vdr/types/interface.go @@ -22,18 +22,19 @@ import ( // DocResolver is the interface that groups all the DID Document read methods type DocResolver interface { - // Get returns the DID document using on the given DID or ErrNotFound if not found. + // Resolve returns the DID document using on the given DID or ErrNotFound if not found. // If metadata is provided then the result is filtered or scoped on that meta data // If metadata is not provided the latest version is returned // If something goes wrong an error is returned. Resolve(DID did.DID, metadata *ResolveMetaData) (*did.Document, *DocumentMetadata, error) } -// DocCreator 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 +// 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) } diff --git a/vdr/vdr.go b/vdr/vdr.go index efb40c1ef3..3ba99c4b7e 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -50,7 +50,7 @@ import ( // 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 an engine which can be started providing an http API server and client and a +// It is also a Runnable, Diagnosable and Configurable Nuts Engine. type VDR struct { Config Config store types.Store @@ -64,30 +64,22 @@ type VDR struct { } var instance *VDR -var oneRegistry sync.Once +var oneVDR sync.Once -// ReloadRegistryIdleTimeout defines the cooling down period after receiving a file watcher notification, before -// the registry is reloaded (from disk). -var ReloadRegistryIdleTimeout time.Duration - -func init() { - ReloadRegistryIdleTimeout = 3 * time.Second -} - -// RegistryInstance returns the singleton VDR -func RegistryInstance() *VDR { +// Instance returns the singleton VDR +func Instance() *VDR { if instance != nil { return instance } - oneRegistry.Do(func() { - instance = NewRegistryInstance(DefaultRegistryConfig(), crypto.Instance(), networkClient.NewNetworkClient()) + oneVDR.Do(func() { + instance = NewVDR(DefaultConfig(), crypto.Instance(), networkClient.NewNetworkClient()) }) return instance } -// NewRegistryInstance creates a new instance -func NewRegistryInstance(config Config, cryptoClient crypto.KeyStore, networkClient pkg.NetworkClient) *VDR { +// 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, From 1b7a000767161e47e978a1bc6aa7cff59cba790e Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Fri, 22 Jan 2021 15:45:56 +0100 Subject: [PATCH 51/54] Fix PR feedback --- core/config.go | 2 +- crypto/api/v1/generated.go | 1 + crypto/crypto.go | 2 +- crypto/hash/{hash.go => sha256.go} | 18 +----- crypto/hash/{hash_test.go => sha256_test.go} | 0 crypto/interface.go | 10 +-- crypto/mock.go | 4 +- crypto/storage/fs.go | 2 +- crypto/test.go | 2 +- docs/_static/vdr/v1.yaml | 68 ++++++++++---------- docs/pages/api.rst | 4 +- vdr/api/v1/generated.go | 5 +- vdr/doc-creator.go | 4 +- vdr/doc-creator_test.go | 8 +-- vdr/engine/engine.go | 4 +- vdr/types/interface.go | 2 +- 16 files changed, 59 insertions(+), 77 deletions(-) rename crypto/hash/{hash.go => sha256.go} (80%) rename crypto/hash/{hash_test.go => sha256_test.go} (100%) diff --git a/core/config.go b/core/config.go index f3d2a0ab27..c8da2ecebd 100644 --- a/core/config.go +++ b/core/config.go @@ -41,7 +41,7 @@ const configFileFlag = "configfile" const loggerLevelFlag = "verbosity" const addressFlag = "address" const defaultLogLevel = "info" -const defaultAddress = "localhost:1323" +const defaultAddress = "http://localhost:1323" const strictModeFlag = "strictmode" const modeFlag = "mode" const identityFlag = "identity" 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/crypto.go b/crypto/crypto.go index 33f23dd38c..87b7ae6f78 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -141,7 +141,7 @@ 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 diff --git a/crypto/hash/hash.go b/crypto/hash/sha256.go similarity index 80% rename from crypto/hash/hash.go rename to crypto/hash/sha256.go index 9dcbf91738..296c919ee2 100644 --- a/crypto/hash/hash.go +++ b/crypto/hash/sha256.go @@ -1,19 +1,3 @@ -/* - * 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 @@ -51,7 +35,7 @@ func SHA256Sum(data []byte) SHA256Hash { return sha256.Sum256(data) } -// String returns the SHA256Hash as a hexidecimal string. +// String returns the SHA256Hash as a hexadecimal string. func (h SHA256Hash) String() string { return hex.EncodeToString(h[:]) } diff --git a/crypto/hash/hash_test.go b/crypto/hash/sha256_test.go similarity index 100% rename from crypto/hash/hash_test.go rename to crypto/hash/sha256_test.go diff --git a/crypto/interface.go b/crypto/interface.go index 02bc84fd49..852a19a77f 100644 --- a/crypto/interface.go +++ b/crypto/interface.go @@ -22,17 +22,17 @@ 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, error) +// KIDNamingFunc is a function passed to New() which generates the kid for the pub/priv key +type KIDNamingFunc func(key crypto.PublicKey) (string, error) // 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 than can be called by a Cmd, Direct or via rest call. +// KeyStore defines the functions that can be called by a Cmd, Direct or via rest call. type KeyStore interface { KeyCreator diff --git a/crypto/mock.go b/crypto/mock.go index 2dd1e3fa84..6b29695a18 100644 --- a/crypto/mock.go +++ b/crypto/mock.go @@ -34,7 +34,7 @@ func (m *MockKeyCreator) EXPECT() *MockKeyCreatorMockRecorder { } // New mocks base method -func (m *MockKeyCreator) New(namingFunc KidNamingFunc) (crypto.PublicKey, string, error) { +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) @@ -73,7 +73,7 @@ func (m *MockKeyStore) EXPECT() *MockKeyStoreMockRecorder { } // New mocks base method -func (m *MockKeyStore) New(namingFunc 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) diff --git a/crypto/storage/fs.go b/crypto/storage/fs.go index 58e177b5ec..df289fc1a7 100644 --- a/crypto/storage/fs.go +++ b/crypto/storage/fs.go @@ -1,6 +1,6 @@ /* * Nuts node - * Copyright (C) 2021. Nuts communityy + * 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 3cafac021a..00b06d2482 100644 --- a/crypto/test.go +++ b/crypto/test.go @@ -29,7 +29,7 @@ func TestCryptoConfig(testDirectory string) Config { } // StringNamingFunc can be used to give a key a simple string name -func StringNamingFunc(name string) KidNamingFunc { +func StringNamingFunc(name string) KIDNamingFunc { return func(key crypto.PublicKey) (string, error) { return name, nil } diff --git a/docs/_static/vdr/v1.yaml b/docs/_static/vdr/v1.yaml index 3aefe97364..ca03cb9499 100644 --- a/docs/_static/vdr/v1.yaml +++ b/docs/_static/vdr/v1.yaml @@ -1,14 +1,14 @@ openapi: "3.0.0" info: - title: Nuts verifiable dataa registry API spec - description: API specification for RPC services available at the nuts-registry + 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" + summary: Creates a new Nuts DID operationId: "createDID" tags: - DID @@ -20,7 +20,7 @@ paths: schema: $ref: '#/components/schemas/DIDDocument' "500": - description: "An error occurred while processing the request." + description: An error occurred while processing the request. content: text/plain: schema: @@ -29,7 +29,7 @@ paths: parameters: - name: did in: path - description: "URL encoded DID." + description: URL encoded DID. required: true example: - "did:nuts:1234" @@ -42,30 +42,30 @@ paths: - DID responses: "200": - description: "DID has been found and returned." + description: DID has been found and returned. content: application/json: $ref: '#/components/schemas/DIDResolutionResult' "400": - description: "given DID is incorrect." + description: Returned in case of malformed DID. content: text/plain: schema: type: string "404": - description: "DID couldn't be found." + description: Corresponding DID document could not be found. content: text/plain: schema: type: string "500": - description: "An error occurred while processing the request." + description: An error occurred while processing the request. content: text/plain: schema: type: string put: - summary: "Updates a Nuts DID Document" + summary: Updates a Nuts DID Document. operationId: "updateDID" tags: - DID @@ -77,25 +77,25 @@ paths: $ref: '#/components/schemas/DIDUpdateRequest' responses: "200": - description: "DID Document has been updated." + 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." + description: DID couldn't be updated because the input conflicts or is malformed. content: text/plain: schema: type: string "404": - description: "DID couldn't be found." + description: Corresponding DID document could not be found. content: text/plain: schema: type: string "500": - description: "An error occurred while processing the request." + description: An error occurred while processing the request. content: text/plain: schema: @@ -120,12 +120,12 @@ components: items: type: string context: - description: Array of URIs + description: List of URIs type: array items: type: string controller: - description: Array of DIDs that have control over the DID Document + description: List of DIDs that have control over the DID Document type: array items: type: string @@ -134,7 +134,7 @@ components: example: "did:nuts:1" type: string service: - description: list of supported services by the DID subject + description: List of supported services by the DID subject type: array items: $ref: '#/components/schemas/Service' @@ -153,20 +153,20 @@ components: - version properties: created: - description: time when DID Document was created in rfc3339 form. + description: Time when DID Document was created in rfc3339 form. type: string hash: - description: sha256 in hex form of the DID document contents + 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 + 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. + description: Time when DID Document was updated in rfc3339 form. type: string version: - description: version of the DID Document, starting at 1 - type: string + description: Version of the DID Document, starting at 1. + type: integer DIDResolutionResult: required: - document @@ -185,13 +185,13 @@ components: - serviceEndpoint properties: id: - description: ID of the service + description: ID of the service. type: string type: - description: the type of the endpoint + description: The type of the endpoint. type: string serviceEndpoint: - description: either a URI or a complex object + description: Either a URI or a complex object. oneOf: - type: string - type: object @@ -204,9 +204,9 @@ components: $ref: '#/components/schemas/DIDDocument' currentHash: type: string - description: "hex encoded hash" + description: The hash of the document in hex format. VerificationMethod: - description: A public key in Jwk form + description: A public key in JWK form. required: - id - type @@ -214,18 +214,16 @@ components: - publicKeyJwk properties: controller: - description: the DID subject this key belongs to + 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. + description: The ID of the key, used as KID in various JWX technologies. type: string publicKeyJwk: - description: the public key as defined in rfc7517 + description: The public key formatted according rfc7517. type: object type: - description: the type of the key + description: The type of the key. example: "JsonWebKey2020" - type: string - - + type: string \ No newline at end of file diff --git a/docs/pages/api.rst b/docs/pages/api.rst index 11bba41834..f6fc4f9f64 100644 --- a/docs/pages/api.rst +++ b/docs/pages/api.rst @@ -14,8 +14,8 @@ Nuts APIs const ui = SwaggerUIBundle({ "dom_id": "#swagger-ui", urls: [ - {url: "../_static/crypto/v1.yaml", name: "crypto"}, - {url: "../_static/vdr/v1.yaml", name: "Verifiable data registry"}, + {url: "../_static/crypto/v1.yaml", name: "Crypto"}, + {url: "../_static/vdr/v1.yaml", name: "Verifiable Data Registry"}, ], presets: [ SwaggerUIBundle.presets.apis, diff --git a/vdr/api/v1/generated.go b/vdr/api/v1/generated.go index b3a925a57c..4f05df5f15 100644 --- a/vdr/api/v1/generated.go +++ b/vdr/api/v1/generated.go @@ -31,7 +31,7 @@ type DIDResolutionResult struct { // DIDUpdateRequest defines model for DIDUpdateRequest. type DIDUpdateRequest struct { - // hex encoded hash + // The hash of the document in hex format. CurrentHash string `json:"currentHash"` // The actual DID Document according to the w3c spec. @@ -500,7 +500,7 @@ type ServerInterface interface { // Resolves a Nuts DID Document // (GET /internal/vdr/v1/did/{did}) GetDID(ctx echo.Context, did string) error - // Updates a Nuts DID Document + // Updates a Nuts DID Document. // (PUT /internal/vdr/v1/did/{did}) UpdateDID(ctx echo.Context, did string) error } @@ -584,3 +584,4 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.PUT(baseURL+"/internal/vdr/v1/did/:did", wrapper.UpdateDID) } + diff --git a/vdr/doc-creator.go b/vdr/doc-creator.go index 44edfcb8ff..39a7146fa0 100644 --- a/vdr/doc-creator.go +++ b/vdr/doc-creator.go @@ -27,7 +27,7 @@ type NutsDocCreator struct { keyCreator nutsCrypto.KeyCreator } -func didKidNamingFunc(pKey crypto.PublicKey) (string, error) { +func didKIDNamingFunc(pKey crypto.PublicKey) (string, error) { ecPKey, ok := pKey.(*ecdsa.PublicKey) if !ok { return "", errors.New("could not generate kid: invalid key type") @@ -68,7 +68,7 @@ func didKidNamingFunc(pKey crypto.PublicKey) (string, error) { // 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) + key, keyID, err := n.keyCreator.New(didKIDNamingFunc) if err != nil { return nil, fmt.Errorf("unable to build did: %w", err) } diff --git a/vdr/doc-creator_test.go b/vdr/doc-creator_test.go index c32616f7a0..0a609af46d 100644 --- a/vdr/doc-creator_test.go +++ b/vdr/doc-creator_test.go @@ -23,7 +23,7 @@ type mockKeyCreator struct { } // 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) { +func (m *mockKeyCreator) New(namingFunc nutsCrypto.KIDNamingFunc) (crypto.PublicKey, string, error) { rawKey, err := jwkToPublicKey(m.t, m.jwkStr) if err != nil { return nil, "", err @@ -72,7 +72,7 @@ func Test_didKidNamingFunc(t *testing.T) { return } - keyID, err := didKidNamingFunc(privateKey.PublicKey) + keyID, err := didKIDNamingFunc(privateKey.PublicKey) if !assert.NoError(t, err) { return } @@ -82,7 +82,7 @@ func Test_didKidNamingFunc(t *testing.T) { t.Run("nok - wrong key type", func(t *testing.T) { privateKey := rsa.PrivateKey{} - keyID, err := didKidNamingFunc(privateKey.PublicKey) + keyID, err := didKIDNamingFunc(privateKey.PublicKey) if !assert.Error(t, err) { return } @@ -93,7 +93,7 @@ func Test_didKidNamingFunc(t *testing.T) { t.Run("nok - empty key", func(t *testing.T) { pubKey := &ecdsa.PublicKey{} - keyID, err := didKidNamingFunc(pubKey) + keyID, err := didKIDNamingFunc(pubKey) assert.Error(t, err) assert.Equal(t, "could not generate kid: empty key curve", err.Error()) assert.Empty(t, keyID) diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go index 84532ca6c8..999880824f 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -95,7 +95,7 @@ func createCmd() *cobra.Command { bytes, _ := json.MarshalIndent(doc, "", " ") - cmd.Printf("Created DID document: %v\n", string(bytes)) + cmd.Printf("Created DID document: %s\n", string(bytes)) return nil }, } @@ -180,8 +180,6 @@ func readFromStdin() ([]byte, error) { } func httpClient() api.HTTPClient { - core.NutsConfig().ServerAddress() - return api.HTTPClient{ ServerAddress: core.NutsConfig().ServerAddress(), Timeout: 5 * time.Second, diff --git a/vdr/types/interface.go b/vdr/types/interface.go index 54cb779e7b..8fcb347aa8 100644 --- a/vdr/types/interface.go +++ b/vdr/types/interface.go @@ -23,7 +23,7 @@ import ( // DocResolver is the interface that groups all the DID Document read methods type DocResolver interface { // Resolve returns the DID document using on the given DID or ErrNotFound if not found. - // If metadata is provided then the result is filtered or scoped on that meta data + // If metadata is provided then the result is filtered or scoped on that metadata // If metadata is not provided the latest version is returned // If something goes wrong an error is returned. Resolve(DID did.DID, metadata *ResolveMetaData) (*did.Document, *DocumentMetadata, error) From 77d63803bc2c287673c65d9c754f57a9ce77a5ff Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Fri, 22 Jan 2021 16:02:16 +0100 Subject: [PATCH 52/54] Wrap errors --- vdr/engine/engine.go | 14 +++++++------- vdr/store/memory.go | 5 +---- vdr/vdr.go | 11 ----------- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go index 999880824f..56aff549b0 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -110,8 +110,8 @@ func updateCmd() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { client := httpClient() - d := args[0] - h := args[1] + dID := args[0] + hash := args[1] var bytes []byte var err error @@ -119,24 +119,24 @@ func updateCmd() *cobra.Command { // read from file bytes, err = ioutil.ReadFile(args[2]) if err != nil { - return fmt.Errorf("failed to read file %s: %s", args[2], err) + 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: %s", err) + 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: %s", err) + return fmt.Errorf("failed to parse DID document: %w", err) } - if _, err = client.Update(d, h, didDoc); err != nil { - return fmt.Errorf("failed to update DID document: %s", err) + if _, err = client.Update(dID, hash, didDoc); err != nil { + return fmt.Errorf("failed to update DID document: %w", err) } cmd.Println("DID document updated") diff --git a/vdr/store/memory.go b/vdr/store/memory.go index 6d34a98cf4..2b4b8cfa14 100644 --- a/vdr/store/memory.go +++ b/vdr/store/memory.go @@ -66,10 +66,7 @@ type memoryEntry struct { } func (me memoryEntry) isDeactivated() bool { - if len(me.document.Controller) == 0 && len(me.document.Authentication) == 0 { - return true - } - return false + return len(me.document.Controller) == 0 && len(me.document.Authentication) == 0 } func (m *memory) Resolve(DID did.DID, metadata *types.ResolveMetaData) (*did.Document, *types.DocumentMetadata, error) { diff --git a/vdr/vdr.go b/vdr/vdr.go index 3ba99c4b7e..528ac4c632 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -59,7 +59,6 @@ type VDR struct { networkAmbassador network.Ambassador configOnce sync.Once _logger *logrus.Entry - closers []chan struct{} didDocCreator types.DocCreator } @@ -106,21 +105,11 @@ func (r *VDR) Configure() error { // Start initiates the routines for auto-updating the data func (r *VDR) Start() error { - if core.NutsConfig().Mode() == core.ServerEngineMode { - r.networkAmbassador.Start() - } return nil } // Shutdown cleans up any leftover go routines func (r *VDR) Shutdown() error { - if core.NutsConfig().Mode() == core.ServerEngineMode { - logging.Log().Debug("Sending close signal to all routines") - for _, ch := range r.closers { - ch <- struct{}{} - } - logging.Log().Info("All routines closed") - } return nil } From 21ab0851416a89710c33efdee740c7584c56d86d Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Mon, 25 Jan 2021 11:41:43 +0100 Subject: [PATCH 53/54] Fix PR feedback * Ignore pem files in the root * Fix http prefix from address * Specify DID documents used in the api should be according to the nuts spec * Removed unused vdr config options from readme * Always return a deep copy from the memory store to prevent modification * Renamed a bunch of vars and types --- .gitignore | 4 +- core/config.go | 2 +- docs/_static/vdr/v1.yaml | 2 +- docs/pages/configuration/options.rst | 15 ++---- go.mod | 2 +- go.sum | 6 +-- vdr/api/v1/generated.go | 4 +- vdr/engine/engine.go | 16 ++++-- vdr/store/memory.go | 51 +++++++++++++----- vdr/store/memory_test.go | 8 +-- vdr/types/common.go | 4 +- vdr/types/interface.go | 18 +++---- vdr/types/mock.go | 80 ++++++++++++++-------------- vdr/vdr.go | 8 +-- 14 files changed, 123 insertions(+), 97 deletions(-) diff --git a/.gitignore b/.gitignore index dd6538382c..3ba1681b55 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,6 @@ data # MacOS .DS_Store -*.pem + +# ignore generated pem files from running the nuts-node executable. +/*.pem diff --git a/core/config.go b/core/config.go index c8da2ecebd..f3d2a0ab27 100644 --- a/core/config.go +++ b/core/config.go @@ -41,7 +41,7 @@ const configFileFlag = "configfile" const loggerLevelFlag = "verbosity" const addressFlag = "address" const defaultLogLevel = "info" -const defaultAddress = "http://localhost:1323" +const defaultAddress = "localhost:1323" const strictModeFlag = "strictmode" const modeFlag = "mode" const identityFlag = "identity" diff --git a/docs/_static/vdr/v1.yaml b/docs/_static/vdr/v1.yaml index ca03cb9499..d1297036d1 100644 --- a/docs/_static/vdr/v1.yaml +++ b/docs/_static/vdr/v1.yaml @@ -105,7 +105,7 @@ components: schemas: DIDDocument: type: object - description: The actual DID Document according to the w3c spec. + description: A DID Document according to the W3C spec following the Nuts Method rules as defined in [Nuts RFC006] required: - id properties: diff --git a/docs/pages/configuration/options.rst b/docs/pages/configuration/options.rst index 4f8eef7e01..603e825142 100755 --- a/docs/pages/configuration/options.rst +++ b/docs/pages/configuration/options.rst @@ -46,15 +46,8 @@ network.nodeID 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. **VDR** -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.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/go.mod b/go.mod index 528d66d483..7dc70828fb 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( 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-20210114144203-c658fb4d0f65 + 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 diff --git a/go.sum b/go.sum index 36c4ba329b..7a947f419f 100644 --- a/go.sum +++ b/go.sum @@ -348,8 +348,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW 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-20210114144203-c658fb4d0f65 h1:0ZsmVFuJpJmcMGM0GGq1V2m4eN7q1ef28GzaCWJ24Oo= -github.com/nuts-foundation/go-did v0.0.0-20210114144203-c658fb4d0f65/go.mod h1:wpgiNMle93QRhzm2Oh4J+yGgbSpw1LoBAcsoUqNr2Bo= +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= @@ -370,8 +370,6 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ 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/pelletier/go-toml v1.5.0 h1:5BakdOZdtKJ1FFk6QdL8iSGrMWsXgchNJcrnarjbmJQ= -github.com/pelletier/go-toml v1.5.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= 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= diff --git a/vdr/api/v1/generated.go b/vdr/api/v1/generated.go index 4f05df5f15..bbabab0a2b 100644 --- a/vdr/api/v1/generated.go +++ b/vdr/api/v1/generated.go @@ -21,7 +21,7 @@ import ( // DIDResolutionResult defines model for DIDResolutionResult. type DIDResolutionResult struct { - // The actual DID Document according to the w3c spec. + // 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. @@ -34,7 +34,7 @@ type DIDUpdateRequest struct { // The hash of the document in hex format. CurrentHash string `json:"currentHash"` - // The actual DID Document according to the w3c spec. + // A DID Document according to the W3C spec following the Nuts Method rules as defined in [Nuts RFC006] Document DIDDocument `json:"document"` } diff --git a/vdr/engine/engine.go b/vdr/engine/engine.go index 56aff549b0..84e76774aa 100644 --- a/vdr/engine/engine.go +++ b/vdr/engine/engine.go @@ -26,6 +26,7 @@ import ( "fmt" "io/ioutil" "os" + "strings" "time" "github.com/nuts-foundation/go-did" @@ -36,7 +37,7 @@ import ( "github.com/spf13/pflag" ) -// NewVDREngine returns the core definition for the registry +// NewVDREngine returns the core definition for the VDR func NewVDREngine() *core.Engine { r := vdr.Instance() @@ -56,7 +57,7 @@ func NewVDREngine() *core.Engine { } func flagSet() *pflag.FlagSet { - flagSet := pflag.NewFlagSet("registry", pflag.ContinueOnError) + 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)) @@ -110,7 +111,7 @@ func updateCmd() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { client := httpClient() - dID := args[0] + id := args[0] hash := args[1] var bytes []byte @@ -135,7 +136,7 @@ func updateCmd() *cobra.Command { return fmt.Errorf("failed to parse DID document: %w", err) } - if _, err = client.Update(dID, hash, didDoc); err != nil { + if _, err = client.Update(id, hash, didDoc); err != nil { return fmt.Errorf("failed to update DID document: %w", err) } @@ -180,8 +181,13 @@ func readFromStdin() ([]byte, error) { } func httpClient() api.HTTPClient { + // TODO: allow for https + addr := core.NutsConfig().ServerAddress() + if !strings.HasPrefix(addr, "http") { + addr = "http://" + addr + } return api.HTTPClient{ - ServerAddress: core.NutsConfig().ServerAddress(), + ServerAddress: addr, Timeout: 5 * time.Second, } } diff --git a/vdr/store/memory.go b/vdr/store/memory.go index 2b4b8cfa14..63009f78b0 100644 --- a/vdr/store/memory.go +++ b/vdr/store/memory.go @@ -16,9 +16,11 @@ 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" ) @@ -69,11 +71,13 @@ func (me memoryEntry) isDeactivated() bool { return len(me.document.Controller) == 0 && len(me.document.Authentication) == 0 } -func (m *memory) Resolve(DID did.DID, metadata *types.ResolveMetaData) (*did.Document, *types.DocumentMetadata, error) { +// 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[DID.String()] + entries, ok := m.store[id.String()] if !ok { return nil, nil, types.ErrNotFound } @@ -104,11 +108,31 @@ func (m *memory) Resolve(DID did.DID, metadata *types.ResolveMetaData) (*did.Doc return nil, nil, err } - return &entry.document, &entry.metadata, nil + // 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 { +func timeSelectionFilter(metadata types.ResolveMetadata) filterFunc { return func(e memoryEntry) bool { if e.metadata.Created.After(*metadata.ResolveTime) { return false @@ -132,17 +156,18 @@ func timeSelectionFilter(metadata types.ResolveMetaData) filterFunc { } } -func (m *memory) Write(DIDDocument did.Document, metadata types.DocumentMetadata) error { +// 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[DIDDocument.ID.String()]; ok { + if _, ok := m.store[document.ID.String()]; ok { return types.ErrDIDAlreadyExists } - m.store[DIDDocument.ID.String()] = versionedEntryList{ + m.store[document.ID.String()] = versionedEntryList{ &memoryEntry{ - document: DIDDocument, + document: document, metadata: metadata, }, } @@ -150,13 +175,15 @@ func (m *memory) Write(DIDDocument did.Document, metadata types.DocumentMetadata return nil } -// Update does not check if the timestamp in the metadata make sense or if the metadata.hash matches the hash +// 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(DID did.DID, current hash.SHA256Hash, next did.Document, metadata *types.DocumentMetadata) error { +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[DID.String()] + entries, ok := m.store[id.String()] if !ok { return types.ErrNotFound } @@ -181,7 +208,7 @@ func (m *memory) Update(DID did.DID, current hash.SHA256Hash, next did.Document, // update next in last document entry.next = newEntry - m.store[DID.String()] = append(entries, newEntry) + m.store[id.String()] = append(entries, newEntry) return nil } diff --git a/vdr/store/memory_test.go b/vdr/store/memory_test.go index 654be165c6..ff8fc56ada 100644 --- a/vdr/store/memory_test.go +++ b/vdr/store/memory_test.go @@ -82,7 +82,7 @@ func TestMemory_Resolve(t *testing.T) { 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{ + d, m, err := store.Resolve(*did1, &types.ResolveMetadata{ ResolveTime: &now, }) if !assert.NoError(t, err) { @@ -94,14 +94,14 @@ func TestMemory_Resolve(t *testing.T) { 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{ + _, _, 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{ + d, m, err := store.Resolve(*did1, &types.ResolveMetadata{ Hash: &h, }) if !assert.NoError(t, err) { @@ -117,7 +117,7 @@ func TestTimeSelectionFilter(t *testing.T) { now := time.Now() later := time.Now().Add(time.Hour * 24) - metadata := types.ResolveMetaData{ + metadata := types.ResolveMetadata{ ResolveTime: &now, } f := timeSelectionFilter(metadata) diff --git a/vdr/types/common.go b/vdr/types/common.go index 48eae548dc..830c9a4d5b 100644 --- a/vdr/types/common.go +++ b/vdr/types/common.go @@ -53,8 +53,8 @@ type DocumentMetadata struct { Hash hash.SHA256Hash `json:"hash"` } -// ResolveMetaData contains metadata for the resolver. -type ResolveMetaData struct { +// 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 diff --git a/vdr/types/interface.go b/vdr/types/interface.go index 8fcb347aa8..a16bfb2ebf 100644 --- a/vdr/types/interface.go +++ b/vdr/types/interface.go @@ -22,17 +22,17 @@ import ( // DocResolver is the interface that groups all the DID Document read methods type DocResolver interface { - // Resolve returns the DID document using on the given DID or ErrNotFound if not found. - // If metadata is provided then the result is filtered or scoped on that metadata - // If metadata is not provided the latest version is returned - // If something goes wrong an error is returned. - Resolve(DID did.DID, metadata *ResolveMetaData) (*did.Document, *DocumentMetadata, error) + // 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 + // 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) @@ -43,7 +43,7 @@ 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(DIDDocument did.Document, metadata DocumentMetadata) error + Write(document did.Document, metadata DocumentMetadata) error } // DocUpdater is the interface that defines functions that alter the state of a DID document @@ -53,7 +53,7 @@ type DocUpdater interface { // 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(DID did.DID, current hash.SHA256Hash, next did.Document, metadata *DocumentMetadata) error + Update(id did.DID, current hash.SHA256Hash, next did.Document, metadata *DocumentMetadata) error } // DocDeactivator is the interface that defines functions to deactivate DID Docs @@ -63,7 +63,7 @@ type DocDeactivator interface { // 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(DID did.DID, current hash.SHA256Hash) + Deactivate(id did.DID, current hash.SHA256Hash) } // Store is the interface that groups all low level VDR DID storage operations. diff --git a/vdr/types/mock.go b/vdr/types/mock.go index 9db473bd29..a1d7c3dd6c 100644 --- a/vdr/types/mock.go +++ b/vdr/types/mock.go @@ -35,9 +35,9 @@ func (m *MockDocResolver) EXPECT() *MockDocResolverMockRecorder { } // Resolve mocks base method -func (m *MockDocResolver) Resolve(DID go_did.DID, metadata *ResolveMetaData) (*go_did.Document, *DocumentMetadata, error) { +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", DID, metadata) + ret := m.ctrl.Call(m, "Resolve", id, metadata) ret0, _ := ret[0].(*go_did.Document) ret1, _ := ret[1].(*DocumentMetadata) ret2, _ := ret[2].(error) @@ -45,9 +45,9 @@ func (m *MockDocResolver) Resolve(DID go_did.DID, metadata *ResolveMetaData) (*g } // Resolve indicates an expected call of Resolve -func (mr *MockDocResolverMockRecorder) Resolve(DID, metadata interface{}) *gomock.Call { +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), DID, metadata) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resolve", reflect.TypeOf((*MockDocResolver)(nil).Resolve), id, metadata) } // MockDocCreator is a mock of DocCreator interface @@ -112,17 +112,17 @@ func (m *MockDocWriter) EXPECT() *MockDocWriterMockRecorder { } // Write mocks base method -func (m *MockDocWriter) Write(DIDDocument go_did.Document, metadata DocumentMetadata) error { +func (m *MockDocWriter) Write(document go_did.Document, metadata DocumentMetadata) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Write", DIDDocument, metadata) + 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(DIDDocument, metadata interface{}) *gomock.Call { +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), DIDDocument, metadata) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockDocWriter)(nil).Write), document, metadata) } // MockDocUpdater is a mock of DocUpdater interface @@ -149,17 +149,17 @@ func (m *MockDocUpdater) EXPECT() *MockDocUpdaterMockRecorder { } // Update mocks base method -func (m *MockDocUpdater) Update(DID go_did.DID, current hash.SHA256Hash, next go_did.Document, metadata *DocumentMetadata) error { +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", DID, current, next, metadata) + 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(DID, current, next, metadata interface{}) *gomock.Call { +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), DID, current, next, metadata) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDocUpdater)(nil).Update), id, current, next, metadata) } // MockDocDeactivator is a mock of DocDeactivator interface @@ -186,15 +186,15 @@ func (m *MockDocDeactivator) EXPECT() *MockDocDeactivatorMockRecorder { } // Deactivate mocks base method -func (m *MockDocDeactivator) Deactivate(DID go_did.DID, current hash.SHA256Hash) { +func (m *MockDocDeactivator) Deactivate(id go_did.DID, current hash.SHA256Hash) { m.ctrl.T.Helper() - m.ctrl.Call(m, "Deactivate", DID, current) + m.ctrl.Call(m, "Deactivate", id, current) } // Deactivate indicates an expected call of Deactivate -func (mr *MockDocDeactivatorMockRecorder) Deactivate(DID, current interface{}) *gomock.Call { +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), DID, current) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deactivate", reflect.TypeOf((*MockDocDeactivator)(nil).Deactivate), id, current) } // MockStore is a mock of Store interface @@ -221,9 +221,9 @@ func (m *MockStore) EXPECT() *MockStoreMockRecorder { } // Resolve mocks base method -func (m *MockStore) Resolve(DID go_did.DID, metadata *ResolveMetaData) (*go_did.Document, *DocumentMetadata, error) { +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", DID, metadata) + ret := m.ctrl.Call(m, "Resolve", id, metadata) ret0, _ := ret[0].(*go_did.Document) ret1, _ := ret[1].(*DocumentMetadata) ret2, _ := ret[2].(error) @@ -231,37 +231,37 @@ func (m *MockStore) Resolve(DID go_did.DID, metadata *ResolveMetaData) (*go_did. } // Resolve indicates an expected call of Resolve -func (mr *MockStoreMockRecorder) Resolve(DID, metadata interface{}) *gomock.Call { +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), DID, metadata) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resolve", reflect.TypeOf((*MockStore)(nil).Resolve), id, metadata) } // Write mocks base method -func (m *MockStore) Write(DIDDocument go_did.Document, metadata DocumentMetadata) error { +func (m *MockStore) Write(document go_did.Document, metadata DocumentMetadata) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Write", DIDDocument, metadata) + 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(DIDDocument, metadata interface{}) *gomock.Call { +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), DIDDocument, metadata) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockStore)(nil).Write), document, metadata) } // Update mocks base method -func (m *MockStore) Update(DID go_did.DID, current hash.SHA256Hash, next go_did.Document, metadata *DocumentMetadata) error { +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", DID, current, next, metadata) + 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(DID, current, next, metadata interface{}) *gomock.Call { +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), DID, current, next, metadata) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockStore)(nil).Update), id, current, next, metadata) } // MockVDR is a mock of VDR interface @@ -288,9 +288,9 @@ func (m *MockVDR) EXPECT() *MockVDRMockRecorder { } // Resolve mocks base method -func (m *MockVDR) Resolve(DID go_did.DID, metadata *ResolveMetaData) (*go_did.Document, *DocumentMetadata, error) { +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", DID, metadata) + ret := m.ctrl.Call(m, "Resolve", id, metadata) ret0, _ := ret[0].(*go_did.Document) ret1, _ := ret[1].(*DocumentMetadata) ret2, _ := ret[2].(error) @@ -298,9 +298,9 @@ func (m *MockVDR) Resolve(DID go_did.DID, metadata *ResolveMetaData) (*go_did.Do } // Resolve indicates an expected call of Resolve -func (mr *MockVDRMockRecorder) Resolve(DID, metadata interface{}) *gomock.Call { +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), DID, metadata) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resolve", reflect.TypeOf((*MockVDR)(nil).Resolve), id, metadata) } // Create mocks base method @@ -319,27 +319,27 @@ func (mr *MockVDRMockRecorder) Create() *gomock.Call { } // Update mocks base method -func (m *MockVDR) Update(DID go_did.DID, current hash.SHA256Hash, next go_did.Document, metadata *DocumentMetadata) error { +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", DID, current, next, metadata) + 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(DID, current, next, metadata interface{}) *gomock.Call { +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), DID, current, next, metadata) + 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(DID go_did.DID, current hash.SHA256Hash) { +func (m *MockVDR) Deactivate(id go_did.DID, current hash.SHA256Hash) { m.ctrl.T.Helper() - m.ctrl.Call(m, "Deactivate", DID, current) + m.ctrl.Call(m, "Deactivate", id, current) } // Deactivate indicates an expected call of Deactivate -func (mr *MockVDRMockRecorder) Deactivate(DID, current interface{}) *gomock.Call { +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), DID, current) + 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 index 528ac4c632..53e4407dbc 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -142,13 +142,13 @@ func (r VDR) Create() (*did.Document, error) { } // Resolve resolves a DID Document based on the DID. -func (r VDR) Resolve(dID did.DID, metadata *types.ResolveMetaData) (*did.Document, *types.DocumentMetadata, error) { - return r.store.Resolve(dID, metadata) +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(dID did.DID, current hash.SHA256Hash, next did.Document, metadata *types.DocumentMetadata) error { - return r.store.Update(dID, current, next, metadata) +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 From 9c144173331a313f3c2f164779ce35757698c0c0 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Mon, 25 Jan 2021 14:07:46 +0100 Subject: [PATCH 54/54] Fix automatic checks Code coverage Naming issues --- vdr/engine/engine_test.go | 20 ++++++++++++++++++-- vdr/store/memory.go | 8 ++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/vdr/engine/engine_test.go b/vdr/engine/engine_test.go index 2cc3bdd3c8..b450b1fe70 100644 --- a/vdr/engine/engine_test.go +++ b/vdr/engine/engine_test.go @@ -26,11 +26,12 @@ import ( "testing" "github.com/nuts-foundation/go-did" - http2 "github.com/nuts-foundation/nuts-node/test/http" - v1 "github.com/nuts-foundation/nuts-node/vdr/api/v1" "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" ) @@ -207,3 +208,18 @@ func TestEngine_Command(t *testing.T) { }) }) } + +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/store/memory.go b/vdr/store/memory.go index 63009f78b0..8735bcd907 100644 --- a/vdr/store/memory.go +++ b/vdr/store/memory.go @@ -112,19 +112,19 @@ func (m *memory) Resolve(id did.DID, metadata *types.ResolveMetadata) (*did.Docu doc := &did.Document{} docMetadata := &types.DocumentMetadata{} - docJson, err := json.Marshal(entry.document) + docJSON, err := json.Marshal(entry.document) if err != nil { return nil, nil, err } - if err = json.Unmarshal(docJson, doc); err != nil { + if err = json.Unmarshal(docJSON, doc); err != nil { return nil, nil, err } - metadataJson, err := json.Marshal(entry.metadata) + metadataJSON, err := json.Marshal(entry.metadata) if err != nil { return nil, nil, err } - if err = json.Unmarshal(metadataJson, docMetadata); err != nil { + if err = json.Unmarshal(metadataJSON, docMetadata); err != nil { return nil, nil, err }