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

Commit

Permalink
Merge remote-tracking branch 'origin/main' into support-key-access-in…
Browse files Browse the repository at this point in the history
…-all-services-ose-122

* origin/main:
  SIP-4: DWN Routes (#113)
  Update README.md
  Add Sip 4 to table (#109)
  Adding SIP4 Doc (#108)

# Conflicts:
#	config/compose.toml
#	config/config.go
#	config/config.toml
#	go.mod
#	go.sum
#	pkg/server/server_test.go
#	pkg/service/service.go
  • Loading branch information
gabe committed Sep 28, 2022
2 parents b47cad6 + ce425c4 commit 04806ff
Show file tree
Hide file tree
Showing 18 changed files with 616 additions and 4 deletions.
8 changes: 6 additions & 2 deletions config/compose.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ methods = ["key"]
[services.schema]
name = "schema"

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

[services.manifest]
name = "manifest"
name = "manifest"

[services.dwn]
name = "dwn"
dwn_endpoint = "http://localhost:4321"
17 changes: 17 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 {
SchemaConfig SchemaServiceConfig `toml:"schema,omitempty"`
CredentialConfig CredentialServiceConfig `toml:"credential,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 @@ -121,6 +122,18 @@ func (m *ManifestServiceConfig) IsEmpty() bool {
return reflect.DeepEqual(m, &ManifestServiceConfig{})
}

type DWNServiceConfig struct {
*BaseServiceConfig
DWNEndpoint string `toml:"dwn_endpoint"`
}

func (d *DWNServiceConfig) IsEmpty() bool {
if d == nil {
return true
}
return reflect.DeepEqual(d, &DWNServiceConfig{})
}

// LoadConfig attempts to load a TOML config file from the given path, and coerce it into our object model.
// Before loading, defaults are applied on certain properties, which are overwritten if specified in the TOML file.
func LoadConfig(path string) (*SSIServiceConfig, error) {
Expand Down Expand Up @@ -181,6 +194,10 @@ func LoadConfig(path string) (*SSIServiceConfig, error) {
ManifestConfig: ManifestServiceConfig{
BaseServiceConfig: &BaseServiceConfig{Name: "manifest"},
},
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 @@ -32,7 +32,7 @@ methods = ["key"]
[services.schema]
name = "schema"

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

[services.manifest]
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 {
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 {
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

0 comments on commit 04806ff

Please sign in to comment.