From a425f8877a39ad14f6e8c9190a7fdd119bfd0f1f Mon Sep 17 00:00:00 2001 From: Neal Roessler Date: Mon, 26 Sep 2022 13:27:45 -0500 Subject: [PATCH 1/4] dwn route for publish manifest, with dwn config for dwn endpoint --- config/compose.toml | 8 ++- config/config.go | 10 +++ config/config.toml | 2 +- go.mod | 2 +- go.sum | 4 ++ pkg/server/framework/request.go | 18 +++++ pkg/server/router/dwn.go | 103 +++++++++++++++++++++++++++++ pkg/server/server.go | 16 +++++ pkg/service/dwn/dwn.go | 65 ++++++++++++++++++ pkg/service/dwn/model.go | 11 +++ pkg/service/framework/framework.go | 1 + pkg/service/service.go | 9 ++- 12 files changed, 244 insertions(+), 5 deletions(-) create mode 100644 pkg/server/router/dwn.go create mode 100644 pkg/service/dwn/dwn.go create mode 100644 pkg/service/dwn/model.go diff --git a/config/compose.toml b/config/compose.toml index e2e0cd0a5..6597c39c4 100644 --- a/config/compose.toml +++ b/config/compose.toml @@ -30,5 +30,9 @@ methods = ["key"] [services.schema] name = "schema" -[serivces.credential] -name = "credential" \ No newline at end of file +[services.credential] +name = "credential" + +[services.dwn] +name = "dwn" +dwn_endpoint = "http://localhost:4321" diff --git a/config/config.go b/config/config.go index d2b26fe7c..9e2bc115d 100644 --- a/config/config.go +++ b/config/config.go @@ -53,6 +53,7 @@ type ServicesConfig struct { CredentialConfig CredentialServiceConfig `toml:"credential,omitempty"` KeyStoreConfig KeyStoreServiceConfig `toml:"keystore,omitempty"` ManifestConfig ManifestServiceConfig `toml:"manifest,omitempty"` + DWNConfig DWNServiceConfig `toml:"dwn,omitempty"` } // BaseServiceConfig represents configurable properties for a specific component of the SSI Service @@ -93,6 +94,11 @@ type ManifestServiceConfig struct { *BaseServiceConfig } +type DWNServiceConfig struct { + *BaseServiceConfig + DWNEndpoint string `toml:"dwn_endpoint"` +} + type KeyStoreServiceConfig struct { *BaseServiceConfig // Service key password. Used by a KDF whose key is used by a symmetric cypher for key encryption. @@ -157,6 +163,10 @@ func LoadConfig(path string) (*SSIServiceConfig, error) { BaseServiceConfig: &BaseServiceConfig{Name: "keystore"}, ServiceKeyPassword: "default-password", }, + DWNConfig: DWNServiceConfig{ + BaseServiceConfig: &BaseServiceConfig{Name: "did"}, + DWNEndpoint: "http://localhost:4321", + }, } } else { // load from TOML file diff --git a/config/config.toml b/config/config.toml index df426cb04..aa5031333 100644 --- a/config/config.toml +++ b/config/config.toml @@ -28,5 +28,5 @@ methods = ["key"] [services.schema] name = "schema" -[serivces.credential] +[services.credential] name = "credential" \ No newline at end of file diff --git a/go.mod b/go.mod index f5bfcb762..c07bdbc3a 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/BurntSushi/toml v1.2.0 - github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220920154030-cbf6b4665a6d + github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220923181557-56fc273d1dcd github.com/ardanlabs/conf v1.5.0 github.com/dimfeld/httptreemux/v5 v5.4.0 github.com/go-playground/locales v0.14.0 diff --git a/go.sum b/go.sum index dece70f00..f7ee5311a 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,13 @@ github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220915152202-18c69f474901 h1:7cT1hMDiWQlsQYNP2U0mxpUjc5AxKVElcAdvTt5amnM= +github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220915152202-18c69f474901/go.mod h1:uXrbtCwqgsbZvL/zM3+DpAOuuIax9qG2aeUhZrGPCck= github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220919021815-57be087eca80 h1:1HBBtzkjsgjCYjMVWi6+785SO+Q7kSOOrA0Njw3JON0= github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220919021815-57be087eca80/go.mod h1:ZUcjj/CHvtmz48c9GapcbavlTRsN9JZ9pKdT+co/EvY= github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220920154030-cbf6b4665a6d h1:NW/9Qg2Z+Yhh6PtzgV/cFwgNz7Sgng3E25IYQiUV34c= github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220920154030-cbf6b4665a6d/go.mod h1:jkbaZT2qU+g6weOHpjPdxuHdmfOApdT8CQg41GEyZhA= +github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220923181557-56fc273d1dcd h1:BTbS7+/QsnxvVhP0LwB3JJxnT/9qHC8msEtku2JF4QY= +github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220923181557-56fc273d1dcd/go.mod h1:jkbaZT2qU+g6weOHpjPdxuHdmfOApdT8CQg41GEyZhA= github.com/ardanlabs/conf v1.5.0 h1:5TwP6Wu9Xi07eLFEpiCUF3oQXh9UzHMDVnD3u/I5d5c= github.com/ardanlabs/conf v1.5.0/go.mod h1:ILsMo9dMqYzCxDjDXTiwMI0IgxOJd0MOiucbQY2wlJw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= diff --git a/pkg/server/framework/request.go b/pkg/server/framework/request.go index 282148e60..7d95f546b 100644 --- a/pkg/server/framework/request.go +++ b/pkg/server/framework/request.go @@ -1,6 +1,7 @@ package framework import ( + "bytes" "errors" "net/http" "reflect" @@ -95,3 +96,20 @@ func Decode(r *http.Request, val interface{}) error { return nil } + +// Post does a post request with data to provided endpoint +func Post(endpoint string, data interface{}) (*http.Response, error) { + // convert response payload to json + jsonData, err := json.Marshal(data) + if err != nil { + return nil, err + } + + resp, err := http.Post(endpoint, "application/json", bytes.NewBuffer(jsonData)) + + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/pkg/server/router/dwn.go b/pkg/server/router/dwn.go new file mode 100644 index 000000000..6b41e625a --- /dev/null +++ b/pkg/server/router/dwn.go @@ -0,0 +1,103 @@ +package router + +import ( + "context" + "fmt" + "github.com/TBD54566975/ssi-sdk/credential/manifest" + "github.com/tbd54566975/ssi-service/pkg/service/dwn" + "net/http" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + "github.com/tbd54566975/ssi-service/pkg/server/framework" + svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework" +) + +type DWNRouter struct { + service *dwn.Service +} + +func NewDWNRouter(s svcframework.Service) (*DWNRouter, error) { + if s == nil { + return nil, errors.New("service cannot be nil") + } + dwnService, ok := s.(*dwn.Service) + if !ok { + return nil, fmt.Errorf("could not create dwn router with service type: %s", s.Type()) + } + + return &DWNRouter{ + service: dwnService, + }, nil +} + +type DWNPostRequest struct { + Manifest manifest.CredentialManifest `json:"manifest" validate:"required"` +} + +type PublishManifestRequest struct { + ManifestID string `json:"manifestId" validate:"required"` +} + +func (req PublishManifestRequest) ToServiceRequest() dwn.DWNPublishManifestRequest { + return dwn.DWNPublishManifestRequest{ + ManifestID: req.ManifestID, + } +} + +type PublishManifestResponse struct { + Manifest manifest.CredentialManifest `json:"manifest" validate:"required"` + Published bool `json:"published" validate:"required"` + ErrorMessage string `json:"errorMessage" validate:"required"` +} + +// PublishManifest godoc +// @Summary Public Manifest to DWN +// @Description Public Manifest to DWN +// @Tags DWNAPI +// @Accept json +// @Produce json +// @Param request body PublishManifestRequest true "request body" +// @Success 201 {object} PublishManifestResponse +// @Failure 400 {string} string "Bad request" +// @Failure 500 {string} string "Internal server error" +// @Router /v1/dwn/manifest [put] +func (dwnr DWNRouter) PublishManifest(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + + if dwnr.service.Config().DWNEndpoint == "" { + errMsg := "could not publish manifest to dwn because dwn endpoint is not configured" + logrus.Error(errMsg) + return framework.NewRequestError(errors.New(errMsg), http.StatusInternalServerError) + } + + var request PublishManifestRequest + if err := framework.Decode(r, &request); err != nil { + errMsg := "invalid publish manifest message request" + logrus.WithError(err).Error(errMsg) + return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest) + } + + req := request.ToServiceRequest() + publishManifestResponse, err := dwnr.service.PublishManifest(req) + + if err != nil { + errMsg := "could not publish manifest to dwn" + logrus.WithError(err).Error(errMsg) + return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError) + } + + dwnReq := DWNPostRequest{Manifest: publishManifestResponse.Manifest} + dwnResp, err := framework.Post(dwnr.service.Config().DWNEndpoint, dwnReq) + + if err != nil || dwnResp == nil { + errMsg := "problem with publishing manifest to downstream dwn" + logrus.WithError(err).Error(errMsg) + return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest) + } + + status := dwnResp.StatusCode >= 200 && dwnResp.StatusCode < 300 + resp := PublishManifestResponse{Manifest: publishManifestResponse.Manifest, Published: status} + + return framework.Respond(ctx, w, resp, http.StatusAccepted) +} diff --git a/pkg/server/server.go b/pkg/server/server.go index 94edf36b2..cc5480974 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -30,6 +30,7 @@ const ( ApplicationsPrefix = "/applications" ResponsesPrefix = "/responses" KeyStorePrefix = "/keys" + DWNPrefix = "/dwn" ) // SSIServer exposes all dependencies needed to run a http server and all its services @@ -96,6 +97,8 @@ func (s *SSIServer) instantiateRouter(service svcframework.Service) error { return s.KeyStoreAPI(service) case svcframework.Manifest: return s.ManifestAPI(service) + case svcframework.DWN: + return s.DWNApi(service) default: return fmt.Errorf("could not instantiate API for service: %s", serviceType) } @@ -170,6 +173,7 @@ func (s *SSIServer) ManifestAPI(service svcframework.Service) (err error) { responsesHandlerPath := V1Prefix + ManifestsPrefix + ResponsesPrefix s.Handle(http.MethodPut, manifestHandlerPath, manifestRouter.CreateManifest) + s.Handle(http.MethodGet, manifestHandlerPath, manifestRouter.GetManifests) s.Handle(http.MethodGet, path.Join(manifestHandlerPath, "/:id"), manifestRouter.GetManifest) s.Handle(http.MethodDelete, path.Join(manifestHandlerPath, "/:id"), manifestRouter.DeleteManifest) @@ -184,3 +188,15 @@ func (s *SSIServer) ManifestAPI(service svcframework.Service) (err error) { s.Handle(http.MethodDelete, path.Join(responsesHandlerPath, "/:id"), manifestRouter.DeleteResponse) return } + +func (s *SSIServer) DWNApi(service svcframework.Service) (err error) { + dwnRouter, err := router.NewDWNRouter(service) + if err != nil { + return util.LoggingErrorMsg(err, "could not create dwn router") + } + + dwnPath := V1Prefix + DWNPrefix + ManifestsPrefix + + s.Handle(http.MethodPut, dwnPath, dwnRouter.PublishManifest) + return +} diff --git a/pkg/service/dwn/dwn.go b/pkg/service/dwn/dwn.go new file mode 100644 index 000000000..71bacafe9 --- /dev/null +++ b/pkg/service/dwn/dwn.go @@ -0,0 +1,65 @@ +package dwn + +import ( + "fmt" + "github.com/sirupsen/logrus" + "github.com/tbd54566975/ssi-service/config" + "github.com/tbd54566975/ssi-service/internal/util" + "github.com/tbd54566975/ssi-service/pkg/service/framework" + manifeststorage "github.com/tbd54566975/ssi-service/pkg/service/manifest/storage" + "github.com/tbd54566975/ssi-service/pkg/storage" +) + +type Service struct { + config config.DWNServiceConfig + manifestStorage manifeststorage.Storage +} + +func (s Service) Type() framework.Type { + return framework.DWN +} + +func (s Service) Status() framework.Status { + if s.manifestStorage == nil { + return framework.Status{ + Status: framework.StatusNotReady, + Message: "no manifestStorage", + } + } + + return framework.Status{Status: framework.StatusReady} +} + +func (s Service) Config() config.DWNServiceConfig { + return s.config +} + +func NewDWNService(config config.DWNServiceConfig, s storage.ServiceStorage) (*Service, error) { + manifestStorage, err := manifeststorage.NewManifestStorage(s) + if err != nil { + errMsg := "could not instantiate manifestStorage for the dwn service" + return nil, util.LoggingErrorMsg(err, errMsg) + } + + return &Service{ + config: config, + manifestStorage: manifestStorage, + }, nil +} + +func (s Service) PublishManifest(request DWNPublishManifestRequest) (*DWNPublishManifestResponse, error) { + logrus.Debugf("publishing manifest to dwn: %+v", request) + + gotManifest, err := s.manifestStorage.GetManifest(request.ManifestID) + + if err != nil { + return nil, util.LoggingErrorMsg(err, fmt.Sprintf("problem retrieving manifest with id %v", request.ManifestID)) + } + + if gotManifest == nil { + return nil, util.LoggingErrorMsg(err, fmt.Sprintf("manifest with id %v not found", request.ManifestID)) + } + + response := DWNPublishManifestResponse{Manifest: gotManifest.Manifest} + return &response, nil +} diff --git a/pkg/service/dwn/model.go b/pkg/service/dwn/model.go new file mode 100644 index 000000000..44ff98b0c --- /dev/null +++ b/pkg/service/dwn/model.go @@ -0,0 +1,11 @@ +package dwn + +import manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" + +type DWNPublishManifestRequest struct { + ManifestID string +} + +type DWNPublishManifestResponse struct { + Manifest manifestsdk.CredentialManifest +} diff --git a/pkg/service/framework/framework.go b/pkg/service/framework/framework.go index 416f0206f..3175dc817 100644 --- a/pkg/service/framework/framework.go +++ b/pkg/service/framework/framework.go @@ -13,6 +13,7 @@ const ( Credential Type = "credential" KeyStore Type = "keystore" Manifest Type = "manifest" + DWN Type = "DWN" StatusReady StatusState = "ready" StatusNotReady StatusState = "not_ready" diff --git a/pkg/service/service.go b/pkg/service/service.go index 111eb89cf..d31a534c6 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -7,6 +7,7 @@ import ( "github.com/tbd54566975/ssi-service/internal/util" "github.com/tbd54566975/ssi-service/pkg/service/credential" "github.com/tbd54566975/ssi-service/pkg/service/did" + "github.com/tbd54566975/ssi-service/pkg/service/dwn" "github.com/tbd54566975/ssi-service/pkg/service/framework" "github.com/tbd54566975/ssi-service/pkg/service/manifest" "github.com/tbd54566975/ssi-service/pkg/service/schema" @@ -84,5 +85,11 @@ func instantiateServices(config config.ServicesConfig) ([]framework.Service, err return nil, util.LoggingErrorMsg(err, errMsg) } - return []framework.Service{didService, schemaService, credentialService, manifestService}, nil + dwnService, err := dwn.NewDWNService(config.DWNConfig, storageProvider) + if err != nil { + errMsg := "could not instantiate the dwn service" + return nil, util.LoggingErrorMsg(err, errMsg) + } + + return []framework.Service{didService, schemaService, credentialService, manifestService, dwnService}, nil } From dd6ae219464811d0354023ef623fad18234f43a2 Mon Sep 17 00:00:00 2001 From: Neal Roessler Date: Mon, 26 Sep 2022 14:10:02 -0500 Subject: [PATCH 2/4] adding tests --- pkg/server/router/dwn_test.go | 48 +++++++++++++++++++++++++++++++ pkg/server/server_test.go | 53 +++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 pkg/server/router/dwn_test.go diff --git a/pkg/server/router/dwn_test.go b/pkg/server/router/dwn_test.go new file mode 100644 index 000000000..e3f43f995 --- /dev/null +++ b/pkg/server/router/dwn_test.go @@ -0,0 +1,48 @@ +package router + +import ( + "github.com/stretchr/testify/assert" + "github.com/tbd54566975/ssi-service/config" + "github.com/tbd54566975/ssi-service/pkg/service/dwn" + "github.com/tbd54566975/ssi-service/pkg/service/framework" + "github.com/tbd54566975/ssi-service/pkg/storage" + "os" + "testing" +) + +func TestDWNRouter(t *testing.T) { + // remove the db file after the test + t.Cleanup(func() { + _ = os.Remove(storage.DBFile) + }) + + t.Run("Nil Service", func(tt *testing.T) { + manifestRouter, err := NewDWNRouter(nil) + assert.Error(tt, err) + assert.Empty(tt, manifestRouter) + assert.Contains(tt, err.Error(), "service cannot be nil") + }) + + t.Run("Bad Service", func(tt *testing.T) { + manifestRouter, err := NewDWNRouter(&testService{}) + assert.Error(tt, err) + assert.Empty(tt, manifestRouter) + assert.Contains(tt, err.Error(), "could not create dwn router with service type: test") + }) + + t.Run("DWN Service Test", func(tt *testing.T) { + bolt, err := storage.NewBoltDB() + assert.NoError(tt, err) + assert.NotEmpty(tt, bolt) + + serviceConfig := config.DWNServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "dwn"}} + manifestService, err := dwn.NewDWNService(serviceConfig, bolt) + assert.NoError(tt, err) + assert.NotEmpty(tt, manifestService) + + // check type and status + assert.Equal(tt, framework.DWN, manifestService.Type()) + assert.Equal(tt, framework.StatusReady, manifestService.Status().Status) + }) + +} diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index b9fccdde2..f2d367b55 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "github.com/TBD54566975/ssi-sdk/credential/exchange" + "github.com/tbd54566975/ssi-service/pkg/service/dwn" "io" "net/http" @@ -1157,6 +1158,58 @@ func newManifestService(t *testing.T, bolt *storage.BoltDB) *router.ManifestRout return manifestRouter } +func TestDWNAPI(t *testing.T) { + t.Run("Test DWN Publish Manifest", func(tt *testing.T) { + bolt, err := storage.NewBoltDB() + + // remove the db file after the test + tt.Cleanup(func() { + _ = bolt.Close() + _ = os.Remove(storage.DBFile) + }) + + dwnService := newDWNService(tt, bolt) + + manifestService := newManifestService(tt, bolt) + + w := httptest.NewRecorder() + + // good request + createManifestRequest := getValidManifestRequest() + + requestValue := newRequestValue(tt, createManifestRequest) + req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests", requestValue) + err = manifestService.CreateManifest(newRequestContext(), w, req) + assert.NoError(tt, err) + + var resp router.CreateManifestResponse + err = json.NewDecoder(w.Body).Decode(&resp) + assert.NoError(tt, err) + + w = httptest.NewRecorder() + + dwnRequest := router.PublishManifestRequest{ManifestID: "WA-DL-CLASS-A"} + dwnRequestValue := newRequestValue(tt, dwnRequest) + dwnReq := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/dwn/manifests", dwnRequestValue) + err = dwnService.PublishManifest(newRequestContext(), w, dwnReq) + + assert.Error(tt, err) + assert.ErrorContains(tt, err, "unsupported protocol scheme") + }) +} +func newDWNService(t *testing.T, bolt *storage.BoltDB) *router.DWNRouter { + dwnService, err := dwn.NewDWNService(config.DWNServiceConfig{DWNEndpoint: "test-endpoint"}, bolt) + require.NoError(t, err) + require.NotEmpty(t, dwnService) + + // create router for service + dwnRouter, err := router.NewDWNRouter(dwnService) + require.NoError(t, err) + require.NotEmpty(t, dwnRouter) + + return dwnRouter +} + func TestKeyStoreAPI(t *testing.T) { t.Run("Test Store Key", func(tt *testing.T) { bolt, err := storage.NewBoltDB() From 1d4984e0e6d3fe4b8032869384b835bfdc47178a Mon Sep 17 00:00:00 2001 From: Neal Roessler Date: Mon, 26 Sep 2022 14:11:06 -0500 Subject: [PATCH 3/4] update tests --- pkg/server/router/dwn_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/server/router/dwn_test.go b/pkg/server/router/dwn_test.go index e3f43f995..44e1ef472 100644 --- a/pkg/server/router/dwn_test.go +++ b/pkg/server/router/dwn_test.go @@ -17,16 +17,16 @@ func TestDWNRouter(t *testing.T) { }) t.Run("Nil Service", func(tt *testing.T) { - manifestRouter, err := NewDWNRouter(nil) + dwnRouter, err := NewDWNRouter(nil) assert.Error(tt, err) - assert.Empty(tt, manifestRouter) + assert.Empty(tt, dwnRouter) assert.Contains(tt, err.Error(), "service cannot be nil") }) t.Run("Bad Service", func(tt *testing.T) { - manifestRouter, err := NewDWNRouter(&testService{}) + dwnRouter, err := NewDWNRouter(&testService{}) assert.Error(tt, err) - assert.Empty(tt, manifestRouter) + assert.Empty(tt, dwnRouter) assert.Contains(tt, err.Error(), "could not create dwn router with service type: test") }) @@ -36,13 +36,13 @@ func TestDWNRouter(t *testing.T) { assert.NotEmpty(tt, bolt) serviceConfig := config.DWNServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "dwn"}} - manifestService, err := dwn.NewDWNService(serviceConfig, bolt) + dwnService, err := dwn.NewDWNService(serviceConfig, bolt) assert.NoError(tt, err) - assert.NotEmpty(tt, manifestService) + assert.NotEmpty(tt, dwnService) // check type and status - assert.Equal(tt, framework.DWN, manifestService.Type()) - assert.Equal(tt, framework.StatusReady, manifestService.Status().Status) + assert.Equal(tt, framework.DWN, dwnService.Type()) + assert.Equal(tt, framework.StatusReady, dwnService.Status().Status) }) } From 3869b8ef2808210201ae6c43e2e0956a85950fc6 Mon Sep 17 00:00:00 2001 From: Neal Roessler Date: Tue, 27 Sep 2022 15:32:05 -0500 Subject: [PATCH 4/4] adding dwn functions to its own package --- config/config.go | 2 +- pkg/dwn/dwn.go | 55 +++++++++++++++++++++++++++++++ pkg/dwn/dwn_test.go | 58 +++++++++++++++++++++++++++++++++ pkg/server/framework/request.go | 18 ---------- pkg/server/router/dwn.go | 29 +++++++---------- pkg/server/server.go | 4 +-- pkg/service/dwn/dwn.go | 12 +++---- 7 files changed, 131 insertions(+), 47 deletions(-) create mode 100644 pkg/dwn/dwn.go create mode 100644 pkg/dwn/dwn_test.go diff --git a/config/config.go b/config/config.go index 9e2bc115d..37a98958b 100644 --- a/config/config.go +++ b/config/config.go @@ -164,7 +164,7 @@ func LoadConfig(path string) (*SSIServiceConfig, error) { ServiceKeyPassword: "default-password", }, DWNConfig: DWNServiceConfig{ - BaseServiceConfig: &BaseServiceConfig{Name: "did"}, + BaseServiceConfig: &BaseServiceConfig{Name: "dwn"}, DWNEndpoint: "http://localhost:4321", }, } diff --git a/pkg/dwn/dwn.go b/pkg/dwn/dwn.go new file mode 100644 index 000000000..c142b760d --- /dev/null +++ b/pkg/dwn/dwn.go @@ -0,0 +1,55 @@ +package dwn + +import ( + "bytes" + "github.com/TBD54566975/ssi-sdk/credential/manifest" + "github.com/goccy/go-json" + "github.com/tbd54566975/ssi-service/internal/util" + "io" + "net/http" +) + +type DWNPublishManifestRequest struct { + Manifest manifest.CredentialManifest `json:"manifest" validate:"required"` +} + +type DWNPublishManifestResponse struct { + Status int `json:"status" validate:"required"` + Response string `json:"response" validate:"required"` +} + +// PublishManifest publishes a CredentialManifest to a DWN +func PublishManifest(endpoint string, manifest manifest.CredentialManifest) (*DWNPublishManifestResponse, error) { + + dwnReq := DWNPublishManifestRequest{Manifest: manifest} + postResp, err := Post(endpoint, dwnReq) + + if err != nil { + return nil, util.LoggingErrorMsg(err, "problem with posting to dwn") + } + + defer postResp.Body.Close() + + b, _ := io.ReadAll(postResp.Body) + body := string(b) + + return &DWNPublishManifestResponse{Status: postResp.StatusCode, Response: body}, nil + +} + +// Post does a post request with data to provided endpoint +func Post(endpoint string, data interface{}) (*http.Response, error) { + // convert response payload to json + jsonData, err := json.Marshal(data) + if err != nil { + return nil, err + } + + resp, err := http.Post(endpoint, "application/json", bytes.NewBuffer(jsonData)) + + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/pkg/dwn/dwn_test.go b/pkg/dwn/dwn_test.go new file mode 100644 index 000000000..c11015623 --- /dev/null +++ b/pkg/dwn/dwn_test.go @@ -0,0 +1,58 @@ +package dwn + +import ( + "github.com/TBD54566975/ssi-sdk/credential/exchange" + manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestPublishManifest(t *testing.T) { + t.Run("Test Publish Manifest", func(tt *testing.T) { + + resp, err := PublishManifest("test-endpoint", getValidManifest()) + assert.Nil(tt, resp) + assert.Error(tt, err) + assert.ErrorContains(tt, err, "problem with posting to dwn") + }) +} + +func getValidManifest() manifestsdk.CredentialManifest { + + return manifestsdk.CredentialManifest{ + ID: "WA-DL-CLASS-A", + SpecVersion: "https://identity.foundation/credential-manifest/spec/v1.0.0/", + Issuer: manifestsdk.Issuer{ + ID: "did:abc:123", + }, + PresentationDefinition: &exchange.PresentationDefinition{ + ID: "pres-def-id", + InputDescriptors: []exchange.InputDescriptor{ + { + ID: "test-id", + Constraints: &exchange.Constraints{ + Fields: []exchange.Field{ + { + Path: []string{".vc.id"}, + }, + }, + }, + }, + }, + }, + OutputDescriptors: []manifestsdk.OutputDescriptor{ + { + ID: "id1", + Schema: "https://test.com/schema", + Name: "good ID", + Description: "it's all good", + }, + { + ID: "id2", + Schema: "https://test.com/schema", + Name: "good ID", + Description: "it's all good", + }, + }, + } +} diff --git a/pkg/server/framework/request.go b/pkg/server/framework/request.go index 7d95f546b..282148e60 100644 --- a/pkg/server/framework/request.go +++ b/pkg/server/framework/request.go @@ -1,7 +1,6 @@ package framework import ( - "bytes" "errors" "net/http" "reflect" @@ -96,20 +95,3 @@ func Decode(r *http.Request, val interface{}) error { return nil } - -// Post does a post request with data to provided endpoint -func Post(endpoint string, data interface{}) (*http.Response, error) { - // convert response payload to json - jsonData, err := json.Marshal(data) - if err != nil { - return nil, err - } - - resp, err := http.Post(endpoint, "application/json", bytes.NewBuffer(jsonData)) - - if err != nil { - return nil, err - } - - return resp, nil -} diff --git a/pkg/server/router/dwn.go b/pkg/server/router/dwn.go index 6b41e625a..8d0a3f28d 100644 --- a/pkg/server/router/dwn.go +++ b/pkg/server/router/dwn.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" + dwnpkg "github.com/tbd54566975/ssi-service/pkg/dwn" "github.com/tbd54566975/ssi-service/pkg/server/framework" svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework" ) @@ -32,10 +33,6 @@ func NewDWNRouter(s svcframework.Service) (*DWNRouter, error) { }, nil } -type DWNPostRequest struct { - Manifest manifest.CredentialManifest `json:"manifest" validate:"required"` -} - type PublishManifestRequest struct { ManifestID string `json:"manifestId" validate:"required"` } @@ -47,9 +44,8 @@ func (req PublishManifestRequest) ToServiceRequest() dwn.DWNPublishManifestReque } type PublishManifestResponse struct { - Manifest manifest.CredentialManifest `json:"manifest" validate:"required"` - Published bool `json:"published" validate:"required"` - ErrorMessage string `json:"errorMessage" validate:"required"` + Manifest manifest.CredentialManifest `json:"manifest" validate:"required"` + DWNResponse dwnpkg.DWNPublishManifestResponse `json:"dwnResponse" validate:"required"` } // PublishManifest godoc @@ -79,25 +75,22 @@ func (dwnr DWNRouter) PublishManifest(ctx context.Context, w http.ResponseWriter } req := request.ToServiceRequest() - publishManifestResponse, err := dwnr.service.PublishManifest(req) + publishManifestResponse, err := dwnr.service.GetManifest(req) - if err != nil { - errMsg := "could not publish manifest to dwn" + if err != nil || &publishManifestResponse.Manifest == nil { + errMsg := "could not retrieve manifest" logrus.WithError(err).Error(errMsg) return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError) } - dwnReq := DWNPostRequest{Manifest: publishManifestResponse.Manifest} - dwnResp, err := framework.Post(dwnr.service.Config().DWNEndpoint, dwnReq) + dwnResp, err := dwnpkg.PublishManifest(dwnr.service.Config().DWNEndpoint, publishManifestResponse.Manifest) - if err != nil || dwnResp == nil { - errMsg := "problem with publishing manifest to downstream dwn" + if err != nil { + errMsg := "could not publish manifest to DWN" logrus.WithError(err).Error(errMsg) - return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest) + return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError) } - status := dwnResp.StatusCode >= 200 && dwnResp.StatusCode < 300 - resp := PublishManifestResponse{Manifest: publishManifestResponse.Manifest, Published: status} - + resp := PublishManifestResponse{Manifest: publishManifestResponse.Manifest, DWNResponse: *dwnResp} return framework.Respond(ctx, w, resp, http.StatusAccepted) } diff --git a/pkg/server/server.go b/pkg/server/server.go index cc5480974..b9f5c6df3 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -98,7 +98,7 @@ func (s *SSIServer) instantiateRouter(service svcframework.Service) error { case svcframework.Manifest: return s.ManifestAPI(service) case svcframework.DWN: - return s.DWNApi(service) + return s.DWNAPI(service) default: return fmt.Errorf("could not instantiate API for service: %s", serviceType) } @@ -189,7 +189,7 @@ func (s *SSIServer) ManifestAPI(service svcframework.Service) (err error) { return } -func (s *SSIServer) DWNApi(service svcframework.Service) (err error) { +func (s *SSIServer) DWNAPI(service svcframework.Service) (err error) { dwnRouter, err := router.NewDWNRouter(service) if err != nil { return util.LoggingErrorMsg(err, "could not create dwn router") diff --git a/pkg/service/dwn/dwn.go b/pkg/service/dwn/dwn.go index 71bacafe9..de11e230a 100644 --- a/pkg/service/dwn/dwn.go +++ b/pkg/service/dwn/dwn.go @@ -47,17 +47,13 @@ func NewDWNService(config config.DWNServiceConfig, s storage.ServiceStorage) (*S }, nil } -func (s Service) PublishManifest(request DWNPublishManifestRequest) (*DWNPublishManifestResponse, error) { - logrus.Debugf("publishing manifest to dwn: %+v", request) +func (s Service) GetManifest(request DWNPublishManifestRequest) (*DWNPublishManifestResponse, error) { + logrus.Debugf("getting manifest: %s", request.ManifestID) gotManifest, err := s.manifestStorage.GetManifest(request.ManifestID) - if err != nil { - return nil, util.LoggingErrorMsg(err, fmt.Sprintf("problem retrieving manifest with id %v", request.ManifestID)) - } - - if gotManifest == nil { - return nil, util.LoggingErrorMsg(err, fmt.Sprintf("manifest with id %v not found", request.ManifestID)) + errMsg := fmt.Sprintf("could not get manifest: %s", request.ManifestID) + return nil, util.LoggingErrorMsg(err, errMsg) } response := DWNPublishManifestResponse{Manifest: gotManifest.Manifest}