Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

SIP-4: DWN Routes #113

Merged
merged 5 commits into from
Sep 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions config/compose.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,9 @@ methods = ["key"]
[services.schema]
name = "schema"

[serivces.credential]
name = "credential"
[services.credential]
name = "credential"

[services.dwn]
name = "dwn"
dwn_endpoint = "http://localhost:4321"
10 changes: 10 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -157,6 +163,10 @@ func LoadConfig(path string) (*SSIServiceConfig, error) {
BaseServiceConfig: &BaseServiceConfig{Name: "keystore"},
ServiceKeyPassword: "default-password",
},
DWNConfig: DWNServiceConfig{
BaseServiceConfig: &BaseServiceConfig{Name: "dwn"},
DWNEndpoint: "http://localhost:4321",
},
}
} else {
// load from TOML file
Expand Down
2 changes: 1 addition & 1 deletion config/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ methods = ["key"]
[services.schema]
name = "schema"

[serivces.credential]
[services.credential]
name = "credential"
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand Down
55 changes: 55 additions & 0 deletions pkg/dwn/dwn.go
Original file line number Diff line number Diff line change
@@ -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
}
58 changes: 58 additions & 0 deletions pkg/dwn/dwn_test.go
Original file line number Diff line number Diff line change
@@ -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",
},
},
}
}
96 changes: 96 additions & 0 deletions pkg/server/router/dwn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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"

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"
)

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 PublishManifestRequest struct {
nitro-neal marked this conversation as resolved.
Show resolved Hide resolved
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"`
DWNResponse dwnpkg.DWNPublishManifestResponse `json:"dwnResponse" 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.GetManifest(req)

if err != nil || &publishManifestResponse.Manifest == nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider an IsEmpty method on publishManifestResponse

errMsg := "could not retrieve manifest"
logrus.WithError(err).Error(errMsg)
return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError)
}

dwnResp, err := dwnpkg.PublishManifest(dwnr.service.Config().DWNEndpoint, publishManifestResponse.Manifest)

if err != nil {
errMsg := "could not publish manifest to DWN"
logrus.WithError(err).Error(errMsg)
return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError)
}

resp := PublishManifestResponse{Manifest: publishManifestResponse.Manifest, DWNResponse: *dwnResp}
return framework.Respond(ctx, w, resp, http.StatusAccepted)
}
48 changes: 48 additions & 0 deletions pkg/server/router/dwn_test.go
Original file line number Diff line number Diff line change
@@ -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) {
dwnRouter, err := NewDWNRouter(nil)
assert.Error(tt, err)
assert.Empty(tt, dwnRouter)
assert.Contains(tt, err.Error(), "service cannot be nil")
})

t.Run("Bad Service", func(tt *testing.T) {
dwnRouter, err := NewDWNRouter(&testService{})
assert.Error(tt, err)
assert.Empty(tt, dwnRouter)
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"}}
dwnService, err := dwn.NewDWNService(serviceConfig, bolt)
assert.NoError(tt, err)
assert.NotEmpty(tt, dwnService)

// check type and status
assert.Equal(tt, framework.DWN, dwnService.Type())
assert.Equal(tt, framework.StatusReady, dwnService.Status().Status)
})

}
16 changes: 16 additions & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand All @@ -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
}
Loading