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

Commit

Permalink
SIP 2 - Create Credential Manifests CRUD (#91)
Browse files Browse the repository at this point in the history
* wip

* adding complete manifest crud route

* cleaning up test

* changing model to have concrete definition instead of mapping

Co-authored-by: Neal Roessler <[email protected]>
Co-authored-by: Gabe <[email protected]>
  • Loading branch information
3 people authored Sep 9, 2022
1 parent ee6df8d commit 62b812c
Show file tree
Hide file tree
Showing 18 changed files with 947 additions and 18 deletions.
5 changes: 5 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type ServicesConfig struct {
SchemaConfig SchemaServiceConfig `toml:"schema,omitempty"`
CredentialConfig CredentialServiceConfig `toml:"credential,omitempty"`
KeyStoreConfig KeyStoreServiceConfig `toml:"keystore,omitempty"`
ManifestConfig ManifestServiceConfig `toml:"manifest,omitempty"`
}

// BaseServiceConfig represents configurable properties for a specific component of the SSI Service
Expand Down Expand Up @@ -88,6 +89,10 @@ type CredentialServiceConfig struct {
// TODO(gabe) supported key and signature types
}

type ManifestServiceConfig struct {
*BaseServiceConfig
}

type KeyStoreServiceConfig struct {
*BaseServiceConfig
// Service key password. Used by a KDF whose key is used by a symmetric cypher for key encryption.
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ require (

require go.etcd.io/bbolt v1.3.6

require github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 // indirect

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ github.com/multiformats/go-multicodec v0.5.0 h1:EgU6cBe/D7WRwQb1KmnBvU7lrcFGMggZ
github.com/multiformats/go-multicodec v0.5.0/go.mod h1:DiY2HFaEp5EhEXb/iYzVAunmyX/aSFMxq2KMKfWEues=
github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY=
github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI=
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
github.com/piprate/json-gold v0.4.1 h1:JYbYN36n6YcAYipKy3ttv3X2HDQPeqWqmwta35NPj04=
github.com/piprate/json-gold v0.4.1/go.mod h1:OK1z7UgtBZk06n2cDE2OSq1kffmjFFp5/2yhLLCz9UM=
Expand Down
184 changes: 184 additions & 0 deletions pkg/server/router/manifest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package router

import (
"context"
"fmt"
"github.com/TBD54566975/ssi-sdk/credential/exchange"
manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest"
"github.com/tbd54566975/ssi-service/pkg/service/manifest"
"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 ManifestRouter struct {
service *manifest.Service
}

func NewManifestRouter(s svcframework.Service) (*ManifestRouter, error) {
if s == nil {
return nil, errors.New("service cannot be nil")
}
credService, ok := s.(*manifest.Service)
if !ok {
return nil, fmt.Errorf("could not create manifest router with service type: %s", s.Type())
}
return &ManifestRouter{
service: credService,
}, nil
}

type CreateManifestRequest struct {
Issuer string `json:"issuer" validate:"required"`
// A context is optional. If not present, we'll apply default, required context values.
Context string `json:"@context"`
OutputDescriptors []manifestsdk.OutputDescriptor `json:"outputDescriptors" validate:"required"`
PresentationDefinition exchange.PresentationDefinition `json:"presentationDefinition" validate:"required"`
}

func (c CreateManifestRequest) ToServiceRequest() manifest.CreateManifestRequest {
return manifest.CreateManifestRequest{
Issuer: c.Issuer,
Context: c.Context,
OutputDescriptors: c.OutputDescriptors,
PresentationDefinition: c.PresentationDefinition,
}
}

type CreateManifestResponse struct {
Manifest manifestsdk.CredentialManifest `json:"manifest"`
}

// CreateManifest godoc
// @Summary Create manifest
// @Description Create manifest
// @Tags ManifestAPI
// @Accept json
// @Produce json
// @Param request body CreateManifestRequest true "request body"
// @Success 201 {object} CreateManifestResponse
// @Failure 400 {string} string "Bad request"
// @Failure 500 {string} string "Internal server error"
// @Router /v1/manifests [put]
func (mr ManifestRouter) CreateManifest(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
var request CreateManifestRequest
if err := framework.Decode(r, &request); err != nil {
errMsg := "invalid create manifest request"
logrus.WithError(err).Error(errMsg)
return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
}

req := request.ToServiceRequest()
createManifestResponse, err := mr.service.CreateManifest(req)
if err != nil {
errMsg := "could not create manifest"
logrus.WithError(err).Error(errMsg)
return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError)
}

resp := CreateManifestResponse{Manifest: createManifestResponse.Manifest}

return framework.Respond(ctx, w, resp, http.StatusCreated)
}

type GetManifestResponse struct {
ID string `json:"id"`
Manifest manifestsdk.CredentialManifest `json:"manifest"`
}

// GetManifest godoc
// @Summary Get manifest
// @Description Get manifest by id
// @Tags ManifestAPI
// @Accept json
// @Produce json
// @Param id path string true "ID"
// @Success 200 {object} GetManifestResponse
// @Failure 400 {string} string "Bad request"
// @Router /v1/manifests/{id} [get]
func (mr ManifestRouter) GetManifest(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
id := framework.GetParam(ctx, IDParam)
if id == nil {
errMsg := "cannot get manifest without ID parameter"
logrus.Error(errMsg)
return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
}

gotManifest, err := mr.service.GetManifest(manifest.GetManifestRequest{ID: *id})
if err != nil {
errMsg := fmt.Sprintf("could not get manifest with id: %s", *id)
logrus.WithError(err).Error(errMsg)
return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
}

resp := GetManifestResponse{
ID: gotManifest.Manifest.ID,
Manifest: gotManifest.Manifest,
}
return framework.Respond(ctx, w, resp, http.StatusOK)
}

type GetManifestsResponse struct {
Manifests []manifestsdk.CredentialManifest `json:"manifests"`
}

// GetManifests godoc
// @Summary Get manifests
// @Description Checks for the presence of a query parameter and calls the associated filtered get method
// @Tags ManifestAPI
// @Accept json
// @Produce json
// @Param issuer query string false "string issuer"
// @Param schema query string false "string schema"
// @Param subject query string false "string subject"
// @Success 200 {object} GetManifestsResponse
// @Failure 400 {string} string "Bad request"
// @Failure 500 {string} string "Internal server error"
// @Router /v1/manifests [get]
func (mr ManifestRouter) GetManifests(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
gotManifests, err := mr.service.GetManifests()

if err != nil {
errMsg := fmt.Sprintf("could not get manifests")
logrus.WithError(err).Error(errMsg)
return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
}

resp := GetManifestsResponse{
Manifests: gotManifests.Manifests,
}

return framework.Respond(ctx, w, resp, http.StatusOK)
}

// DeleteManifest godoc
// @Summary Delete manifests
// @Description Delete manifest by ID
// @Tags ManifestAPI
// @Accept json
// @Produce json
// @Param id path string true "ID"
// @Success 200 {string} string "OK"
// @Failure 400 {string} string "Bad request"
// @Failure 500 {string} string "Internal server error"
// @Router /v1/manifests/{id} [delete]
func (mr ManifestRouter) DeleteManifest(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
id := framework.GetParam(ctx, IDParam)
if id == nil {
errMsg := "cannot delete manifest without ID parameter"
logrus.Error(errMsg)
return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
}

if err := mr.service.DeleteManifest(manifest.DeleteManifestRequest{ID: *id}); err != nil {
errMsg := fmt.Sprintf("could not delete manifest with id: %s", *id)
logrus.WithError(err).Error(errMsg)
return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError)
}

return framework.Respond(ctx, w, nil, http.StatusOK)
}
95 changes: 95 additions & 0 deletions pkg/server/router/manifest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package router

import (
"github.com/TBD54566975/ssi-sdk/credential/exchange"
manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest"
"github.com/stretchr/testify/assert"
"github.com/tbd54566975/ssi-service/config"
"github.com/tbd54566975/ssi-service/pkg/service/framework"
"github.com/tbd54566975/ssi-service/pkg/service/manifest"
"github.com/tbd54566975/ssi-service/pkg/storage"
"os"
"testing"
)

func TestManifestRouter(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 := NewManifestRouter(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 := NewManifestRouter(&testService{})
assert.Error(tt, err)
assert.Empty(tt, manifestRouter)
assert.Contains(tt, err.Error(), "could not create manifest router with service type: test")
})

t.Run("Manifest Service Test", func(tt *testing.T) {
bolt, err := storage.NewBoltDB()
assert.NoError(tt, err)
assert.NotEmpty(tt, bolt)

serviceConfig := config.ManifestServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "manifest"}}
manifestService, err := manifest.NewManifestService(serviceConfig, bolt)
assert.NoError(tt, err)
assert.NotEmpty(tt, manifestService)

// check type and status
assert.Equal(tt, framework.Manifest, manifestService.Type())
assert.Equal(tt, framework.StatusReady, manifestService.Status().Status)

// good request
createManifestRequest := getValidManifestRequest()

createdManifest, err := manifestService.CreateManifest(createManifestRequest)
assert.NoError(tt, err)
assert.NotEmpty(tt, createdManifest)
assert.NotEmpty(tt, createdManifest.Manifest)
})
}

func getValidManifestRequest() manifest.CreateManifestRequest {
createManifestRequest := manifest.CreateManifestRequest{
Issuer: "did:abc:123",
Context: "context123",
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",
},
},
}

return createManifestRequest
}
1 change: 1 addition & 0 deletions pkg/server/router/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ func (s *testService) Config() config.ServicesConfig {
SchemaConfig: config.SchemaServiceConfig{},
CredentialConfig: config.CredentialServiceConfig{},
KeyStoreConfig: config.KeyStoreServiceConfig{},
ManifestConfig: config.ManifestServiceConfig{},
}
}
6 changes: 3 additions & 3 deletions pkg/server/router/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ type GetSchemaResponse struct {
Schema schemalib.VCJSONSchema `json:"schema,omitempty"`
}

// GetSchemaByID godoc
// GetSchema godoc
// @Summary Get Schema
// @Description Get schema by ID
// @Tags SchemaAPI
Expand All @@ -110,7 +110,7 @@ type GetSchemaResponse struct {
// @Success 200 {object} GetSchemaResponse
// @Failure 400 {string} string "Bad request"
// @Router /v1/schemas/{id} [get]
func (sr SchemaRouter) GetSchemaByID(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
func (sr SchemaRouter) GetSchema(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
id := framework.GetParam(ctx, IDParam)
if id == nil {
errMsg := "cannot get schema without ID parameter"
Expand All @@ -119,7 +119,7 @@ func (sr SchemaRouter) GetSchemaByID(ctx context.Context, w http.ResponseWriter,
}

// TODO(gabe) differentiate between internal errors and not found schemas
gotSchema, err := sr.service.GetSchemaByID(schema.GetSchemaByIDRequest{ID: *id})
gotSchema, err := sr.service.GetSchema(schema.GetSchemaRequest{ID: *id})
if err != nil {
errMsg := fmt.Sprintf("could not get schema with id: %s", *id)
logrus.WithError(err).Error(errMsg)
Expand Down
4 changes: 2 additions & 2 deletions pkg/server/router/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestSchemaRouter(t *testing.T) {
assert.Equal(tt, 0, len(gotSchemas.Schemas))

// get schema that doesn't exist
_, err = schemaService.GetSchemaByID(schema.GetSchemaByIDRequest{ID: "bad"})
_, err = schemaService.GetSchema(schema.GetSchemaRequest{ID: "bad"})
assert.Error(tt, err)
assert.Contains(tt, err.Error(), "error getting schema")

Expand All @@ -75,7 +75,7 @@ func TestSchemaRouter(t *testing.T) {
assert.Equal(tt, "simple schema", createdSchema.Schema.Name)

// get schema by ID
gotSchema, err := schemaService.GetSchemaByID(schema.GetSchemaByIDRequest{ID: createdSchema.ID})
gotSchema, err := schemaService.GetSchema(schema.GetSchemaRequest{ID: createdSchema.ID})
assert.NoError(tt, err)
assert.NotEmpty(tt, gotSchema)
assert.EqualValues(tt, createdSchema.Schema, gotSchema.Schema)
Expand Down
Loading

0 comments on commit 62b812c

Please sign in to comment.