From 229a6de938459eac7c1453e4db81603ef60a9069 Mon Sep 17 00:00:00 2001 From: Neal Roessler Date: Tue, 13 Sep 2022 16:46:51 -0500 Subject: [PATCH 1/8] Adding credential application CRUD and validation --- pkg/server/router/manifest.go | 154 ++++++++++++++- pkg/server/router/manifest_test.go | 32 ++- pkg/server/server.go | 33 ++-- pkg/server/server_test.go | 251 ++++++++++++++++++++---- pkg/service/manifest/manifest.go | 134 +++++++++++++ pkg/service/manifest/model.go | 36 +++- pkg/service/manifest/storage/bolt.go | 79 +++++++- pkg/service/manifest/storage/storage.go | 14 ++ pkg/service/service.go | 2 +- 9 files changed, 674 insertions(+), 61 deletions(-) diff --git a/pkg/server/router/manifest.go b/pkg/server/router/manifest.go index 6a0589285..53463f20a 100644 --- a/pkg/server/router/manifest.go +++ b/pkg/server/router/manifest.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "github.com/TBD54566975/ssi-sdk/credential/exchange" + exchangensdk "github.com/TBD54566975/ssi-sdk/credential/exchange" + applicationsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" "github.com/tbd54566975/ssi-service/pkg/service/manifest" "net/http" @@ -23,15 +25,16 @@ func NewManifestRouter(s svcframework.Service) (*ManifestRouter, error) { if s == nil { return nil, errors.New("service cannot be nil") } - credService, ok := s.(*manifest.Service) + manifestService, 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, + service: manifestService, }, nil } +// Manifest type CreateManifestRequest struct { Issuer string `json:"issuer" validate:"required"` // A context is optional. If not present, we'll apply default, required context values. @@ -182,3 +185,150 @@ func (mr ManifestRouter) DeleteManifest(ctx context.Context, w http.ResponseWrit return framework.Respond(ctx, w, nil, http.StatusOK) } + +// Application +type CreateApplicationRequest struct { + ManifestID string `json:"manifestId" validate:"required"` + PresentationSubmission exchangensdk.PresentationSubmission `json:"presentationSubmission" validate:"required"` +} + +func (c CreateApplicationRequest) ToServiceRequest() manifest.CreateApplicationRequest { + return manifest.CreateApplicationRequest{ + PresentationSubmission: c.PresentationSubmission, + ManifestID: c.ManifestID, + } +} + +type CreateApplicationResponse struct { + Application applicationsdk.CredentialApplication `json:"application"` +} + +// CreateApplication godoc +// @Summary Create application +// @Description Create application +// @Tags ApplicationAPI +// @Accept json +// @Produce json +// @Param request body CreateApplicationRequest true "request body" +// @Success 201 {object} CreateApplicationResponse +// @Failure 400 {string} string "Bad request" +// @Failure 500 {string} string "Internal server error" +// @Router /v1/manifests/applications [put] +func (ar ManifestRouter) CreateApplication(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + var request CreateApplicationRequest + if err := framework.Decode(r, &request); err != nil { + errMsg := "invalid create application request" + logrus.WithError(err).Error(errMsg) + return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest) + } + + req := request.ToServiceRequest() + createApplicationResponse, err := ar.service.CreateApplication(req) + if err != nil { + errMsg := "could not create application" + logrus.WithError(err).Error(errMsg) + return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError) + } + + resp := CreateApplicationResponse{Application: createApplicationResponse.Application} + + return framework.Respond(ctx, w, resp, http.StatusCreated) +} + +type GetApplicationResponse struct { + ID string `json:"id"` + Application applicationsdk.CredentialApplication `json:"application"` +} + +// GetApplication godoc +// @Summary Get application +// @Description Get application by id +// @Tags ApplicationAPI +// @Accept json +// @Produce json +// @Param id path string true "ID" +// @Success 200 {object} GetApplicationResponse +// @Failure 400 {string} string "Bad request" +// @Router /v1/manifests/applications/{id} [get] +func (ar ManifestRouter) GetApplication(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + id := framework.GetParam(ctx, IDParam) + if id == nil { + errMsg := "cannot get application without ID parameter" + logrus.Error(errMsg) + return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest) + } + + gotApplication, err := ar.service.GetApplication(manifest.GetApplicationRequest{ID: *id}) + if err != nil { + errMsg := fmt.Sprintf("could not get application with id: %s", *id) + logrus.WithError(err).Error(errMsg) + return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest) + } + + resp := GetApplicationResponse{ + ID: gotApplication.Application.Application.ID, + Application: gotApplication.Application, + } + return framework.Respond(ctx, w, resp, http.StatusOK) +} + +type GetApplicationsResponse struct { + Applications []applicationsdk.CredentialApplication `json:"applications"` +} + +// GetApplications godoc +// @Summary Get applications +// @Description Checks for the presence of a query parameter and calls the associated filtered get method +// @Tags ApplicationAPI +// @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} GetApplicationsResponse +// @Failure 400 {string} string "Bad request" +// @Failure 500 {string} string "Internal server error" +// @Router /v1/manifests/applications [get] +func (ar ManifestRouter) GetApplications(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + gotApplications, err := ar.service.GetApplications() + + if err != nil { + errMsg := fmt.Sprintf("could not get applications") + logrus.WithError(err).Error(errMsg) + return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest) + } + + resp := GetApplicationsResponse{ + Applications: gotApplications.Applications, + } + + return framework.Respond(ctx, w, resp, http.StatusOK) +} + +// DeleteApplication godoc +// @Summary Delete applications +// @Description Delete application by ID +// @Tags ApplicationAPI +// @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/applications/{id} [delete] +func (ar ManifestRouter) DeleteApplication(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + id := framework.GetParam(ctx, IDParam) + if id == nil { + errMsg := "cannot delete application without ID parameter" + logrus.Error(errMsg) + return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest) + } + + if err := ar.service.DeleteApplication(manifest.DeleteApplicationRequest{ID: *id}); err != nil { + errMsg := fmt.Sprintf("could not delete application 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) +} diff --git a/pkg/server/router/manifest_test.go b/pkg/server/router/manifest_test.go index 57b564b54..99f12693c 100644 --- a/pkg/server/router/manifest_test.go +++ b/pkg/server/router/manifest_test.go @@ -46,14 +46,23 @@ func TestManifestRouter(t *testing.T) { assert.Equal(tt, framework.Manifest, manifestService.Type()) assert.Equal(tt, framework.StatusReady, manifestService.Status().Status) - // good request + // good manifest request createManifestRequest := getValidManifestRequest() createdManifest, err := manifestService.CreateManifest(createManifestRequest) assert.NoError(tt, err) assert.NotEmpty(tt, createdManifest) assert.NotEmpty(tt, createdManifest.Manifest) + + // good application request + createApplicationRequest := getValidApplicationRequest(createdManifest.Manifest.ID, createManifestRequest.PresentationDefinition.InputDescriptors[0].ID) + + createdApplication, err := manifestService.CreateApplication(createApplicationRequest) + assert.NoError(tt, err) + assert.NotEmpty(tt, createdManifest) + assert.NotEmpty(tt, createdApplication.Application.Application.ID) }) + } func getValidManifestRequest() manifest.CreateManifestRequest { @@ -93,3 +102,24 @@ func getValidManifestRequest() manifest.CreateManifestRequest { return createManifestRequest } + +func getValidApplicationRequest(manifestId string, submissionDescriptorId string) manifest.CreateApplicationRequest { + + createApplicationRequest := manifest.CreateApplicationRequest{ + + ManifestID: manifestId, + PresentationSubmission: exchange.PresentationSubmission{ + ID: "psid", + DefinitionID: "definitionId", + DescriptorMap: []exchange.SubmissionDescriptor{ + { + ID: submissionDescriptorId, + Format: "jwt", + Path: "path", + }, + }, + }, + } + + return createApplicationRequest +} diff --git a/pkg/server/server.go b/pkg/server/server.go index 96bda5ee3..a15ccea52 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -20,14 +20,15 @@ import ( ) const ( - HealthPrefix = "/health" - ReadinessPrefix = "/readiness" - V1Prefix = "/v1" - DIDsPrefix = "/dids" - SchemasPrefix = "/schemas" - CredentialsPrefix = "/credentials" - ManifestsPrefix = "/manifests" - KeyStorePrefix = "/keys" + HealthPrefix = "/health" + ReadinessPrefix = "/readiness" + V1Prefix = "/v1" + DIDsPrefix = "/dids" + SchemasPrefix = "/schemas" + CredentialsPrefix = "/credentials" + ManifestsPrefix = "/manifests" + ApplicationsPrefix = "/applications" + KeyStorePrefix = "/keys" ) // SSIServer exposes all dependencies needed to run a http server and all its services @@ -163,11 +164,17 @@ func (s *SSIServer) ManifestAPI(service svcframework.Service) (err error) { return util.LoggingErrorMsg(err, "could not create manifest router") } - handlerPath := V1Prefix + ManifestsPrefix + manifestHandlerPath := V1Prefix + ManifestsPrefix + applicationsHandlerPath := V1Prefix + ManifestsPrefix + ApplicationsPrefix - s.Handle(http.MethodPut, handlerPath, manifestRouter.CreateManifest) - s.Handle(http.MethodGet, handlerPath, manifestRouter.GetManifests) - s.Handle(http.MethodGet, path.Join(handlerPath, "/:id"), manifestRouter.GetManifest) - s.Handle(http.MethodDelete, path.Join(handlerPath, "/:id"), manifestRouter.DeleteManifest) + 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) + + s.Handle(http.MethodPut, applicationsHandlerPath, manifestRouter.CreateApplication) + s.Handle(http.MethodGet, applicationsHandlerPath, manifestRouter.GetApplications) + s.Handle(http.MethodGet, path.Join(applicationsHandlerPath, "/:id"), manifestRouter.GetApplication) + s.Handle(http.MethodDelete, path.Join(applicationsHandlerPath, "/:id"), manifestRouter.DeleteApplication) return } diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 6854dc80f..8d19ffd16 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -765,39 +765,7 @@ func TestManifestAPI(t *testing.T) { w.Flush() // good request - createManifestRequest := router.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", - }, - }, - } + createManifestRequest := getValidManifestRequest() requestValue := newRequestValue(tt, createManifestRequest) req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests", requestValue) @@ -957,6 +925,202 @@ func TestManifestAPI(t *testing.T) { assert.Error(tt, err) assert.Contains(tt, err.Error(), fmt.Sprintf("could not get manifest with id: %s", resp.Manifest.ID)) }) + + t.Run("Test Create Application", func(tt *testing.T) { + bolt, err := storage.NewBoltDB() + + // remove the db file after the test + tt.Cleanup(func() { + _ = bolt.Close() + _ = os.Remove(storage.DBFile) + }) + + manifestService := newManifestService(tt, bolt) + + // missing required field: OutputDescriptors + badManifestRequest := router.CreateApplicationRequest{ + ManifestID: "id123", + } + + badRequestValue := newRequestValue(tt, badManifestRequest) + req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", badRequestValue) + w := httptest.NewRecorder() + + err = manifestService.CreateApplication(newRequestContext(), w, req) + assert.Error(tt, err) + assert.Contains(tt, err.Error(), "invalid create application request") + + // reset the http recorder + w.Flush() + + // 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) + + assert.NotEmpty(tt, resp.Manifest) + assert.Equal(tt, resp.Manifest.Issuer.ID, "did:abc:123") + + // good application request + createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) + + applicationRequestValue := newRequestValue(tt, createApplicationRequest) + req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + err = manifestService.CreateApplication(newRequestContext(), w, req) + + var appResp router.CreateApplicationResponse + err = json.NewDecoder(w.Body).Decode(&appResp) + assert.NoError(tt, err) + + assert.NotEmpty(tt, appResp.Application) + assert.Equal(tt, appResp.Application.Application.ManifestID, resp.Manifest.ID) + + }) + + t.Run("Test Get Application By ID and Get Applications", func(tt *testing.T) { + bolt, err := storage.NewBoltDB() + + // remove the db file after the test + tt.Cleanup(func() { + _ = bolt.Close() + _ = os.Remove(storage.DBFile) + }) + + manifestService := newManifestService(tt, bolt) + + w := httptest.NewRecorder() + + // get a application that doesn't exit + req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/applications/bad", nil) + err = manifestService.GetApplication(newRequestContext(), w, req) + assert.Error(tt, err) + assert.Contains(tt, err.Error(), "cannot get application without ID parameter") + + // reset recorder between calls + w.Flush() + + // good manifest 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) + + // good application request + createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) + + applicationRequestValue := newRequestValue(tt, createApplicationRequest) + req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + err = manifestService.CreateApplication(newRequestContext(), w, req) + + var appResp router.CreateApplicationResponse + err = json.NewDecoder(w.Body).Decode(&appResp) + assert.NoError(tt, err) + + // get application by id + req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", appResp.Application.Application.ID), nil) + err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": appResp.Application.Application.ID}), w, req) + assert.NoError(tt, err) + + var getApplicationResp router.GetApplicationResponse + err = json.NewDecoder(w.Body).Decode(&getApplicationResp) + assert.NoError(tt, err) + assert.NotEmpty(tt, getApplicationResp) + assert.Equal(tt, resp.Manifest.ID, getApplicationResp.Application.Application.ManifestID) + + // good application request #2 + createApplicationRequest = getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) + + applicationRequestValue = newRequestValue(tt, createApplicationRequest) + req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + err = manifestService.CreateApplication(newRequestContext(), w, req) + + var appRespTwo router.CreateApplicationResponse + err = json.NewDecoder(w.Body).Decode(&appRespTwo) + assert.NoError(tt, err) + + req = httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + err = manifestService.GetApplications(newRequestContext(), w, req) + + var getApplicationsResp router.GetApplicationsResponse + err = json.NewDecoder(w.Body).Decode(&getApplicationsResp) + assert.NoError(tt, err) + assert.NotEmpty(tt, getApplicationsResp) + + assert.Len(tt, getApplicationsResp.Applications, 2) + }) + + t.Run("Test Delete Application", func(tt *testing.T) { + bolt, err := storage.NewBoltDB() + + // remove the db file after the test + tt.Cleanup(func() { + _ = bolt.Close() + _ = os.Remove(storage.DBFile) + }) + + manifestService := newManifestService(tt, bolt) + + // good manifest request + createManifestRequest := getValidManifestRequest() + + requestValue := newRequestValue(tt, createManifestRequest) + req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests", requestValue) + w := httptest.NewRecorder() + 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) + + // good application request + createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) + + applicationRequestValue := newRequestValue(tt, createApplicationRequest) + req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + err = manifestService.CreateApplication(newRequestContext(), w, req) + + var appResp router.CreateApplicationResponse + err = json.NewDecoder(w.Body).Decode(&appResp) + assert.NoError(tt, err) + + // get the application + req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", appResp.Application.Application.ID), nil) + err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": appResp.Application.Application.ID}), w, req) + assert.NoError(tt, err) + + var getApplicationResp router.GetApplicationResponse + err = json.NewDecoder(w.Body).Decode(&getApplicationResp) + assert.NoError(tt, err) + assert.NotEmpty(tt, getApplicationResp) + assert.Equal(tt, resp.Manifest.ID, getApplicationResp.Application.Application.ManifestID) + + // delete the application + req = httptest.NewRequest(http.MethodDelete, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", getApplicationResp.Application.Application.ID), nil) + err = manifestService.DeleteApplication(newRequestContextWithParams(map[string]string{"id": getApplicationResp.Application.Application.ID}), w, req) + assert.NoError(tt, err) + + w.Flush() + + // get it back + req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", appResp.Application.Application.ID), nil) + err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": appResp.Application.Application.ID}), w, req) + assert.Error(tt, err) + assert.Contains(tt, err.Error(), fmt.Sprintf("could not get application with id: %s", appResp.Application.Application.ID)) + }) } func newManifestService(t *testing.T, bolt *storage.BoltDB) *router.ManifestRouter { @@ -1148,3 +1312,24 @@ func getValidManifestRequest() router.CreateManifestRequest { return createManifestRequest } + +func getValidApplicationRequest(manifestId string, submissionDescriptorId string) router.CreateApplicationRequest { + + createApplicationRequest := router.CreateApplicationRequest{ + + ManifestID: manifestId, + PresentationSubmission: exchange.PresentationSubmission{ + ID: "psid", + DefinitionID: "definitionId", + DescriptorMap: []exchange.SubmissionDescriptor{ + { + ID: submissionDescriptorId, + Format: "jwt", + Path: "path", + }, + }, + }, + } + + return createApplicationRequest +} diff --git a/pkg/service/manifest/manifest.go b/pkg/service/manifest/manifest.go index 8abb7354b..e9b9858c6 100644 --- a/pkg/service/manifest/manifest.go +++ b/pkg/service/manifest/manifest.go @@ -49,6 +49,7 @@ func NewManifestService(config config.ManifestServiceConfig, s storage.ServiceSt }, nil } +// Manifests func (s Service) CreateManifest(request CreateManifestRequest) (*CreateManifestResponse, error) { logrus.Debugf("creating manifest: %+v", request) @@ -163,3 +164,136 @@ func (s Service) DeleteManifest(request DeleteManifestRequest) error { return nil } + +// TODO: (Neal) Add entire validation framework in place of these validation checks +func isValidApplication(s Service, request CreateApplicationRequest, ps exchange.PresentationSubmission) error { + + gotManifest, err := s.storage.GetManifest(request.ManifestID) + + if err != nil { + return util.LoggingErrorMsg(err, "problem with retrieving manifest during application validation") + } + + if gotManifest == nil { + return util.LoggingNewError(fmt.Sprintf("application is not valid. A manifest does not exist with id: %s", request.ManifestID)) + } + + inputDescriptors := gotManifest.Manifest.PresentationDefinition.InputDescriptors + inputDescriptorIds := map[string]bool{} + + for _, inputDescriptor := range inputDescriptors { + inputDescriptorIds[inputDescriptor.ID] = true + } + + for _, submissionDescriptor := range ps.DescriptorMap { + if inputDescriptorIds[submissionDescriptor.ID] != true { + return util.LoggingNewError("application is not valid. The submission descriptor ids do not match the input descriptor ids") + } + } + + return nil +} + +// Applications +func (s Service) CreateApplication(request CreateApplicationRequest) (*CreateApplicationResponse, error) { + + // parse OutputDescriptors + psJsonString, err := json.Marshal(request.PresentationSubmission) + if err != nil { + errMsg := "could not marshal request presentation submission" + return nil, util.LoggingErrorMsg(err, errMsg) + } + + ps := exchange.PresentationSubmission{} + err = json.Unmarshal(psJsonString, &ps) + if err != nil { + errMsg := "could not unmarshal presentation submission" + return nil, util.LoggingErrorMsg(err, errMsg) + } + + // validate + if err := isValidApplication(s, request, ps); err != nil { + errMsg := fmt.Sprintf("could not validate application") + return nil, util.LoggingErrorMsg(err, errMsg) + } + + // build credential application + builder := manifest.NewCredentialApplicationBuilder(request.ManifestID) + + // TODO: (Neal) Add dynamic claim formats + if err := builder.SetApplicationClaimFormat(exchange.ClaimFormat{ + JWT: &exchange.JWTType{Alg: []crypto.SignatureAlgorithm{crypto.EdDSA}}, + }); err != nil { + errMsg := fmt.Sprintf("could not build application when setting claim format") + return nil, util.LoggingErrorMsg(err, errMsg) + } + + builder.SetPresentationSubmission(ps) + + credApp, err := builder.Build() + if err != nil { + errMsg := "could not build application" + return nil, util.LoggingErrorMsg(err, errMsg) + } + + // store the application + storageRequest := manifeststorage.StoredApplication{ + ID: credApp.Application.ID, + Application: *credApp, + ManifestID: request.ManifestID, + } + + if err := s.storage.StoreApplication(storageRequest); err != nil { + errMsg := "could not store application" + return nil, util.LoggingErrorMsg(err, errMsg) + } + + // return the result + response := CreateApplicationResponse{Application: *credApp} + return &response, nil +} + +func (s Service) GetApplication(request GetApplicationRequest) (*GetApplicationResponse, error) { + + logrus.Debugf("getting application: %s", request.ID) + + gotCred, err := s.storage.GetApplication(request.ID) + if err != nil { + errMsg := fmt.Sprintf("could not get application: %s", request.ID) + return nil, util.LoggingErrorMsg(err, errMsg) + } + + response := GetApplicationResponse{Application: gotCred.Application} + return &response, nil +} + +func (s Service) GetApplications() (*GetApplicationsResponse, error) { + + logrus.Debugf("getting application(s)") + + gotCreds, err := s.storage.GetApplications() + if err != nil { + errMsg := fmt.Sprintf("could not get application(s)") + return nil, util.LoggingErrorMsg(err, errMsg) + } + + var apps []manifest.CredentialApplication + for _, cred := range gotCreds { + apps = append(apps, cred.Application) + } + + response := GetApplicationsResponse{Applications: apps} + return &response, nil +} + +func (s Service) DeleteApplication(request DeleteApplicationRequest) error { + + logrus.Debugf("deleting application: %s", request.ID) + + if err := s.storage.DeleteApplication(request.ID); err != nil { + errMsg := fmt.Sprintf("could not delete application with id: %s", request.ID) + return util.LoggingErrorMsg(err, errMsg) + } + + return nil +} diff --git a/pkg/service/manifest/model.go b/pkg/service/manifest/model.go index 8ad3105ae..d6dc4527c 100644 --- a/pkg/service/manifest/model.go +++ b/pkg/service/manifest/model.go @@ -1,16 +1,18 @@ package manifest import ( - "github.com/TBD54566975/ssi-sdk/credential/exchange" + exchangesdk "github.com/TBD54566975/ssi-sdk/credential/exchange" + applicationsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" ) +// Manifest type CreateManifestRequest struct { Issuer string // A context is optional. If not present, we'll apply default, required context values. Context string OutputDescriptors []manifestsdk.OutputDescriptor - PresentationDefinition exchange.PresentationDefinition + PresentationDefinition exchangesdk.PresentationDefinition } type CreateManifestResponse struct { @@ -25,10 +27,6 @@ type GetManifestResponse struct { Manifest manifestsdk.CredentialManifest } -type GetManifestByIssuerRequest struct { - Issuer string -} - type GetManifestsResponse struct { Manifests []manifestsdk.CredentialManifest } @@ -36,3 +34,29 @@ type GetManifestsResponse struct { type DeleteManifestRequest struct { ID string } + +// Application +type CreateApplicationRequest struct { + ManifestID string + PresentationSubmission exchangesdk.PresentationSubmission +} + +type CreateApplicationResponse struct { + Application applicationsdk.CredentialApplication +} + +type GetApplicationRequest struct { + ID string +} + +type GetApplicationResponse struct { + Application applicationsdk.CredentialApplication +} + +type GetApplicationsResponse struct { + Applications []applicationsdk.CredentialApplication +} + +type DeleteApplicationRequest struct { + ID string +} diff --git a/pkg/service/manifest/storage/bolt.go b/pkg/service/manifest/storage/bolt.go index 6be43ede0..087b36104 100644 --- a/pkg/service/manifest/storage/bolt.go +++ b/pkg/service/manifest/storage/bolt.go @@ -9,7 +9,8 @@ import ( ) const ( - namespace = "manifest" + manifestNamespace = "manifest" + applicationNamespace = "application" ) type BoltManifestStorage struct { @@ -36,11 +37,11 @@ func (b BoltManifestStorage) StoreManifest(manifest StoredManifest) error { logrus.WithError(err).Error(errMsg) return errors.Wrapf(err, errMsg) } - return b.db.Write(namespace, id, manifestBytes) + return b.db.Write(manifestNamespace, id, manifestBytes) } func (b BoltManifestStorage) GetManifest(id string) (*StoredManifest, error) { - manifestBytes, err := b.db.Read(namespace, id) + manifestBytes, err := b.db.Read(manifestNamespace, id) if err != nil { errMsg := fmt.Sprintf("could not get manifest: %s", id) logrus.WithError(err).Error(errMsg) @@ -62,7 +63,7 @@ func (b BoltManifestStorage) GetManifest(id string) (*StoredManifest, error) { // GetManifests attempts to get all stored manifests. It will return those it can even if it has trouble with some. func (b BoltManifestStorage) GetManifests() ([]StoredManifest, error) { - gotManifests, err := b.db.ReadAll(namespace) + gotManifests, err := b.db.ReadAll(manifestNamespace) if err != nil { errMsg := "could not get all manifests" logrus.WithError(err).Error(errMsg) @@ -83,10 +84,78 @@ func (b BoltManifestStorage) GetManifests() ([]StoredManifest, error) { } func (b BoltManifestStorage) DeleteManifest(id string) error { - if err := b.db.Delete(namespace, id); err != nil { + if err := b.db.Delete(manifestNamespace, id); err != nil { errMsg := fmt.Sprintf("could not delete manifest: %s", id) logrus.WithError(err).Error(errMsg) return errors.Wrapf(err, errMsg) } return nil } + +func (b BoltManifestStorage) StoreApplication(application StoredApplication) error { + id := application.Application.Application.ID + if id == "" { + err := errors.New("could not store application without an ID") + logrus.WithError(err).Error() + return err + } + applicationBytes, err := json.Marshal(application) + if err != nil { + errMsg := fmt.Sprintf("could not store application: %s", id) + logrus.WithError(err).Error(errMsg) + return errors.Wrapf(err, errMsg) + } + return b.db.Write(applicationNamespace, id, applicationBytes) +} + +func (b BoltManifestStorage) GetApplication(id string) (*StoredApplication, error) { + applicationBytes, err := b.db.Read(applicationNamespace, id) + if err != nil { + errMsg := fmt.Sprintf("could not get application: %s", id) + logrus.WithError(err).Error(errMsg) + return nil, errors.Wrapf(err, errMsg) + } + if len(applicationBytes) == 0 { + err := fmt.Errorf("application not found with id: %s", id) + logrus.WithError(err).Error("could not get application from storage") + return nil, err + } + var stored StoredApplication + if err := json.Unmarshal(applicationBytes, &stored); err != nil { + errMsg := fmt.Sprintf("could not unmarshal stored application: %s", id) + logrus.WithError(err).Error(errMsg) + return nil, errors.Wrapf(err, errMsg) + } + return &stored, nil +} + +// GetApplications attempts to get all stored applications. It will return those it can even if it has trouble with some. +func (b BoltManifestStorage) GetApplications() ([]StoredApplication, error) { + gotApplications, err := b.db.ReadAll(applicationNamespace) + if err != nil { + errMsg := "could not get all applications" + logrus.WithError(err).Error(errMsg) + return nil, errors.Wrap(err, errMsg) + } + if len(gotApplications) == 0 { + logrus.Info("no applications to get") + return nil, nil + } + var stored []StoredApplication + for _, applicationBytes := range gotApplications { + var nextapplication StoredApplication + if err := json.Unmarshal(applicationBytes, &nextapplication); err == nil { + stored = append(stored, nextapplication) + } + } + return stored, nil +} + +func (b BoltManifestStorage) DeleteApplication(id string) error { + if err := b.db.Delete(applicationNamespace, id); err != nil { + errMsg := fmt.Sprintf("could not delete application: %s", id) + logrus.WithError(err).Error(errMsg) + return errors.Wrapf(err, errMsg) + } + return nil +} diff --git a/pkg/service/manifest/storage/storage.go b/pkg/service/manifest/storage/storage.go index b78a39c10..192e4bce1 100644 --- a/pkg/service/manifest/storage/storage.go +++ b/pkg/service/manifest/storage/storage.go @@ -14,11 +14,25 @@ type StoredManifest struct { Issuer string `json:"issuer"` } +type StoredApplication struct { + ID string `json:"id"` + Application manifest.CredentialApplication `json:"application"` + ManifestID string `json:"manifest_id"` + Format string `json:"format"` +} + type Storage interface { + // Manifest StoreManifest(manifest StoredManifest) error GetManifest(id string) (*StoredManifest, error) GetManifests() ([]StoredManifest, error) DeleteManifest(id string) error + + // Application + StoreApplication(application StoredApplication) error + GetApplication(id string) (*StoredApplication, error) + GetApplications() ([]StoredApplication, error) + DeleteApplication(id string) error } // NewManifestStorage finds the manifest storage impl for a given ServiceStorage value diff --git a/pkg/service/service.go b/pkg/service/service.go index 8945daa2d..111eb89cf 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -2,13 +2,13 @@ package service import ( "fmt" - "github.com/tbd54566975/ssi-service/pkg/service/manifest" "github.com/tbd54566975/ssi-service/config" "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/framework" + "github.com/tbd54566975/ssi-service/pkg/service/manifest" "github.com/tbd54566975/ssi-service/pkg/service/schema" "github.com/tbd54566975/ssi-service/pkg/storage" ) From 2331a978e1c69be2f9d3fc0ba8e13e9bb9e2c3df Mon Sep 17 00:00:00 2001 From: Neal Roessler Date: Thu, 15 Sep 2022 14:59:13 -0500 Subject: [PATCH 2/8] return response and cred --- go.mod | 8 +- go.sum | 8 + pkg/server/router/manifest.go | 155 ++++++++-- pkg/server/router/manifest_test.go | 16 +- pkg/server/server.go | 8 +- pkg/server/server_test.go | 396 ++++++++++++------------ pkg/service/manifest/manifest.go | 151 ++++++--- pkg/service/manifest/model.go | 30 +- pkg/service/manifest/storage/bolt.go | 77 ++++- pkg/service/manifest/storage/storage.go | 19 +- 10 files changed, 578 insertions(+), 290 deletions(-) diff --git a/go.mod b/go.mod index 176345f94..1cbbfe5a0 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.17 require ( github.com/BurntSushi/toml v1.2.0 - github.com/TBD54566975/ssi-sdk v0.0.0-20220719010135-e2fdcfb80e49 + github.com/TBD54566975/ssi-sdk v0.0.1-alpha.0.20220915152202-18c69f474901 github.com/ardanlabs/conf v1.5.0 github.com/dimfeld/httptreemux/v5 v5.4.0 github.com/go-playground/locales v0.14.0 @@ -28,7 +28,7 @@ require github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 // indi require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/validator/v10 v10.11.0 // indirect @@ -42,7 +42,7 @@ require ( github.com/multiformats/go-base32 v0.0.3 // indirect github.com/multiformats/go-base36 v0.1.0 // indirect github.com/multiformats/go-multibase v0.1.1 // indirect - github.com/multiformats/go-multicodec v0.5.0 // indirect + github.com/multiformats/go-multicodec v0.6.0 // indirect github.com/multiformats/go-varint v0.0.6 // indirect github.com/piprate/json-gold v0.4.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -53,7 +53,7 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect go.opentelemetry.io/otel v1.9.0 go.opentelemetry.io/otel/sdk v1.9.0 - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect diff --git a/go.sum b/go.sum index bde0148d9..3992e309b 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ 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.0-20220719010135-e2fdcfb80e49 h1:BozkJ4jg1nZyTzBs/ltNq+KtTuveZDh48Ax85cVh0M4= github.com/TBD54566975/ssi-sdk v0.0.0-20220719010135-e2fdcfb80e49/go.mod h1:uWJkLbobBINP2QFVFL5kku5GtCmbmggzCHh8sTH5NYs= +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/ardanlabs/conf v1.5.0 h1:5TwP6Wu9Xi07eLFEpiCUF3oQXh9UzHMDVnD3u/I5d5c= github.com/ardanlabs/conf v1.5.0/go.mod h1:ILsMo9dMqYzCxDjDXTiwMI0IgxOJd0MOiucbQY2wlJw= github.com/bits-and-blooms/bitset v1.2.2/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= @@ -13,6 +15,8 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/dimfeld/httptreemux/v5 v5.4.0 h1:IiHYEjh+A7pYbhWyjmGnj5HZK6gpOOvyBXCJ+BE8/Gs= github.com/dimfeld/httptreemux/v5 v5.4.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -73,6 +77,8 @@ github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyD github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= github.com/multiformats/go-multicodec v0.5.0 h1:EgU6cBe/D7WRwQb1KmnBvU7lrcFGMggZVTPtOW9dDHs= github.com/multiformats/go-multicodec v0.5.0/go.mod h1:DiY2HFaEp5EhEXb/iYzVAunmyX/aSFMxq2KMKfWEues= +github.com/multiformats/go-multicodec v0.6.0 h1:KhH2kSuCARyuJraYMFxrNO3DqIaYhOdS039kbhgVwpE= +github.com/multiformats/go-multicodec v0.6.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6yC1BqO550bjhWsJbZlw= 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= @@ -137,6 +143,8 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/pkg/server/router/manifest.go b/pkg/server/router/manifest.go index 53463f20a..96532683f 100644 --- a/pkg/server/router/manifest.go +++ b/pkg/server/router/manifest.go @@ -3,9 +3,9 @@ package router import ( "context" "fmt" + "github.com/TBD54566975/ssi-sdk/credential" "github.com/TBD54566975/ssi-sdk/credential/exchange" exchangensdk "github.com/TBD54566975/ssi-sdk/credential/exchange" - applicationsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" "github.com/tbd54566975/ssi-service/pkg/service/manifest" "net/http" @@ -34,7 +34,6 @@ func NewManifestRouter(s svcframework.Service) (*ManifestRouter, error) { }, nil } -// Manifest type CreateManifestRequest struct { Issuer string `json:"issuer" validate:"required"` // A context is optional. If not present, we'll apply default, required context values. @@ -186,58 +185,58 @@ func (mr ManifestRouter) DeleteManifest(ctx context.Context, w http.ResponseWrit return framework.Respond(ctx, w, nil, http.StatusOK) } -// Application -type CreateApplicationRequest struct { +type SubmitApplicationRequest struct { ManifestID string `json:"manifestId" validate:"required"` PresentationSubmission exchangensdk.PresentationSubmission `json:"presentationSubmission" validate:"required"` } -func (c CreateApplicationRequest) ToServiceRequest() manifest.CreateApplicationRequest { - return manifest.CreateApplicationRequest{ +func (c SubmitApplicationRequest) ToServiceRequest() manifest.SubmitApplicationRequest { + return manifest.SubmitApplicationRequest{ PresentationSubmission: c.PresentationSubmission, ManifestID: c.ManifestID, } } -type CreateApplicationResponse struct { - Application applicationsdk.CredentialApplication `json:"application"` +type SubmitApplicationResponse struct { + Response manifestsdk.CredentialResponse `json:"response"` + Credential credential.VerifiableCredential `json:"credential"` } -// CreateApplication godoc -// @Summary Create application -// @Description Create application +// SubmitApplication godoc +// @Summary Submit application +// @Description Submit application // @Tags ApplicationAPI // @Accept json // @Produce json -// @Param request body CreateApplicationRequest true "request body" -// @Success 201 {object} CreateApplicationResponse +// @Param request body SubmitApplicationRequest true "request body" +// @Success 201 {object} SubmitApplicationResponse // @Failure 400 {string} string "Bad request" // @Failure 500 {string} string "Internal server error" // @Router /v1/manifests/applications [put] -func (ar ManifestRouter) CreateApplication(ctx context.Context, w http.ResponseWriter, r *http.Request) error { - var request CreateApplicationRequest +func (mr ManifestRouter) SubmitApplication(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + var request SubmitApplicationRequest if err := framework.Decode(r, &request); err != nil { - errMsg := "invalid create application request" + errMsg := "invalid submit application request" logrus.WithError(err).Error(errMsg) return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest) } req := request.ToServiceRequest() - createApplicationResponse, err := ar.service.CreateApplication(req) + submitApplicationResponse, err := mr.service.SubmitApplication(req) if err != nil { - errMsg := "could not create application" + errMsg := "could not submit application" logrus.WithError(err).Error(errMsg) return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError) } - resp := CreateApplicationResponse{Application: createApplicationResponse.Application} + resp := SubmitApplicationResponse{Response: submitApplicationResponse.Response, Credential: submitApplicationResponse.Credential} return framework.Respond(ctx, w, resp, http.StatusCreated) } type GetApplicationResponse struct { - ID string `json:"id"` - Application applicationsdk.CredentialApplication `json:"application"` + ID string `json:"id"` + Application manifestsdk.CredentialApplication `json:"application"` } // GetApplication godoc @@ -250,7 +249,7 @@ type GetApplicationResponse struct { // @Success 200 {object} GetApplicationResponse // @Failure 400 {string} string "Bad request" // @Router /v1/manifests/applications/{id} [get] -func (ar ManifestRouter) GetApplication(ctx context.Context, w http.ResponseWriter, r *http.Request) error { +func (mr ManifestRouter) GetApplication(ctx context.Context, w http.ResponseWriter, r *http.Request) error { id := framework.GetParam(ctx, IDParam) if id == nil { errMsg := "cannot get application without ID parameter" @@ -258,7 +257,7 @@ func (ar ManifestRouter) GetApplication(ctx context.Context, w http.ResponseWrit return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest) } - gotApplication, err := ar.service.GetApplication(manifest.GetApplicationRequest{ID: *id}) + gotApplication, err := mr.service.GetApplication(manifest.GetApplicationRequest{ID: *id}) if err != nil { errMsg := fmt.Sprintf("could not get application with id: %s", *id) logrus.WithError(err).Error(errMsg) @@ -266,14 +265,14 @@ func (ar ManifestRouter) GetApplication(ctx context.Context, w http.ResponseWrit } resp := GetApplicationResponse{ - ID: gotApplication.Application.Application.ID, + ID: gotApplication.Application.ID, Application: gotApplication.Application, } return framework.Respond(ctx, w, resp, http.StatusOK) } type GetApplicationsResponse struct { - Applications []applicationsdk.CredentialApplication `json:"applications"` + Applications []manifestsdk.CredentialApplication `json:"applications"` } // GetApplications godoc @@ -289,8 +288,8 @@ type GetApplicationsResponse struct { // @Failure 400 {string} string "Bad request" // @Failure 500 {string} string "Internal server error" // @Router /v1/manifests/applications [get] -func (ar ManifestRouter) GetApplications(ctx context.Context, w http.ResponseWriter, r *http.Request) error { - gotApplications, err := ar.service.GetApplications() +func (mr ManifestRouter) GetApplications(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + gotApplications, err := mr.service.GetApplications() if err != nil { errMsg := fmt.Sprintf("could not get applications") @@ -316,7 +315,7 @@ func (ar ManifestRouter) GetApplications(ctx context.Context, w http.ResponseWri // @Failure 400 {string} string "Bad request" // @Failure 500 {string} string "Internal server error" // @Router /v1/manifests/applications/{id} [delete] -func (ar ManifestRouter) DeleteApplication(ctx context.Context, w http.ResponseWriter, r *http.Request) error { +func (mr ManifestRouter) DeleteApplication(ctx context.Context, w http.ResponseWriter, r *http.Request) error { id := framework.GetParam(ctx, IDParam) if id == nil { errMsg := "cannot delete application without ID parameter" @@ -324,7 +323,7 @@ func (ar ManifestRouter) DeleteApplication(ctx context.Context, w http.ResponseW return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest) } - if err := ar.service.DeleteApplication(manifest.DeleteApplicationRequest{ID: *id}); err != nil { + if err := mr.service.DeleteApplication(manifest.DeleteApplicationRequest{ID: *id}); err != nil { errMsg := fmt.Sprintf("could not delete application with id: %s", *id) logrus.WithError(err).Error(errMsg) return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError) @@ -332,3 +331,101 @@ func (ar ManifestRouter) DeleteApplication(ctx context.Context, w http.ResponseW return framework.Respond(ctx, w, nil, http.StatusOK) } + +type GetResponseResponse struct { + ID string `json:"id"` + Response manifestsdk.CredentialResponse `json:"response"` +} + +// GetResponse godoc +// @Summary Get response +// @Description Get response by id +// @Tags ResponseAPI +// @Accept json +// @Produce json +// @Param id path string true "ID" +// @Success 200 {object} GetResponseResponse +// @Failure 400 {string} string "Bad request" +// @Router /v1/manifests/responses/{id} [get] +func (mr ManifestRouter) GetResponse(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + id := framework.GetParam(ctx, IDParam) + if id == nil { + errMsg := "cannot get response without ID parameter" + logrus.Error(errMsg) + return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest) + } + + gotResponse, err := mr.service.GetResponse(manifest.GetResponseRequest{ID: *id}) + if err != nil { + errMsg := fmt.Sprintf("could not get response with id: %s", *id) + logrus.WithError(err).Error(errMsg) + return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest) + } + + resp := GetResponseResponse{ + ID: gotResponse.Response.ID, + Response: gotResponse.Response, + } + return framework.Respond(ctx, w, resp, http.StatusOK) +} + +type GetResponsesResponse struct { + Responses []manifestsdk.CredentialResponse `json:"responses"` +} + +// GetResponses godoc +// @Summary Get responses +// @Description Checks for the presence of a query parameter and calls the associated filtered get method +// @Tags ResponseAPI +// @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} GetResponsesResponse +// @Failure 400 {string} string "Bad request" +// @Failure 500 {string} string "Internal server error" +// @Router /v1/manifests/responses [get] +func (mr ManifestRouter) GetResponses(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + gotResponses, err := mr.service.GetResponses() + + if err != nil { + errMsg := fmt.Sprintf("could not get responses") + logrus.WithError(err).Error(errMsg) + return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest) + } + + resp := GetResponsesResponse{ + Responses: gotResponses.Responses, + } + + return framework.Respond(ctx, w, resp, http.StatusOK) +} + +// DeleteResponse godoc +// @Summary Delete responses +// @Description Delete response by ID +// @Tags ResponseAPI +// @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/responses/{id} [delete] +func (mr ManifestRouter) DeleteResponse(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + id := framework.GetParam(ctx, IDParam) + if id == nil { + errMsg := "cannot delete response without ID parameter" + logrus.Error(errMsg) + return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest) + } + + if err := mr.service.DeleteResponse(manifest.DeleteResponseRequest{ID: *id}); err != nil { + errMsg := fmt.Sprintf("could not delete response 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) +} diff --git a/pkg/server/router/manifest_test.go b/pkg/server/router/manifest_test.go index 99f12693c..5ee6f2cc6 100644 --- a/pkg/server/router/manifest_test.go +++ b/pkg/server/router/manifest_test.go @@ -55,12 +55,12 @@ func TestManifestRouter(t *testing.T) { assert.NotEmpty(tt, createdManifest.Manifest) // good application request - createApplicationRequest := getValidApplicationRequest(createdManifest.Manifest.ID, createManifestRequest.PresentationDefinition.InputDescriptors[0].ID) - - createdApplication, err := manifestService.CreateApplication(createApplicationRequest) - assert.NoError(tt, err) - assert.NotEmpty(tt, createdManifest) - assert.NotEmpty(tt, createdApplication.Application.Application.ID) + //createApplicationRequest := getValidApplicationRequest(createdManifest.Manifest.ID, createManifestRequest.PresentationDefinition.InputDescriptors[0].ID) + // + //createdApplication, err := manifestService.SubmitApplication(createApplicationRequest) + //assert.NoError(tt, err) + //assert.NotEmpty(tt, createdManifest) + //assert.NotEmpty(tt, createdApplication.Response.ID) }) } @@ -103,9 +103,9 @@ func getValidManifestRequest() manifest.CreateManifestRequest { return createManifestRequest } -func getValidApplicationRequest(manifestId string, submissionDescriptorId string) manifest.CreateApplicationRequest { +func getValidApplicationRequest(manifestId string, submissionDescriptorId string) manifest.SubmitApplicationRequest { - createApplicationRequest := manifest.CreateApplicationRequest{ + createApplicationRequest := manifest.SubmitApplicationRequest{ ManifestID: manifestId, PresentationSubmission: exchange.PresentationSubmission{ diff --git a/pkg/server/server.go b/pkg/server/server.go index a15ccea52..94edf36b2 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -28,6 +28,7 @@ const ( CredentialsPrefix = "/credentials" ManifestsPrefix = "/manifests" ApplicationsPrefix = "/applications" + ResponsesPrefix = "/responses" KeyStorePrefix = "/keys" ) @@ -166,15 +167,20 @@ func (s *SSIServer) ManifestAPI(service svcframework.Service) (err error) { manifestHandlerPath := V1Prefix + ManifestsPrefix applicationsHandlerPath := V1Prefix + ManifestsPrefix + ApplicationsPrefix + 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) - s.Handle(http.MethodPut, applicationsHandlerPath, manifestRouter.CreateApplication) + s.Handle(http.MethodPut, applicationsHandlerPath, manifestRouter.SubmitApplication) s.Handle(http.MethodGet, applicationsHandlerPath, manifestRouter.GetApplications) s.Handle(http.MethodGet, path.Join(applicationsHandlerPath, "/:id"), manifestRouter.GetApplication) s.Handle(http.MethodDelete, path.Join(applicationsHandlerPath, "/:id"), manifestRouter.DeleteApplication) + + s.Handle(http.MethodGet, responsesHandlerPath, manifestRouter.GetResponses) + s.Handle(http.MethodGet, path.Join(responsesHandlerPath, "/:id"), manifestRouter.GetResponse) + s.Handle(http.MethodDelete, path.Join(responsesHandlerPath, "/:id"), manifestRouter.DeleteResponse) return } diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 8d19ffd16..1a055e875 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -925,202 +925,202 @@ func TestManifestAPI(t *testing.T) { assert.Error(tt, err) assert.Contains(tt, err.Error(), fmt.Sprintf("could not get manifest with id: %s", resp.Manifest.ID)) }) - - t.Run("Test Create Application", func(tt *testing.T) { - bolt, err := storage.NewBoltDB() - - // remove the db file after the test - tt.Cleanup(func() { - _ = bolt.Close() - _ = os.Remove(storage.DBFile) - }) - - manifestService := newManifestService(tt, bolt) - - // missing required field: OutputDescriptors - badManifestRequest := router.CreateApplicationRequest{ - ManifestID: "id123", - } - - badRequestValue := newRequestValue(tt, badManifestRequest) - req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", badRequestValue) - w := httptest.NewRecorder() - - err = manifestService.CreateApplication(newRequestContext(), w, req) - assert.Error(tt, err) - assert.Contains(tt, err.Error(), "invalid create application request") - - // reset the http recorder - w.Flush() - - // 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) - - assert.NotEmpty(tt, resp.Manifest) - assert.Equal(tt, resp.Manifest.Issuer.ID, "did:abc:123") - - // good application request - createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) - - applicationRequestValue := newRequestValue(tt, createApplicationRequest) - req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) - err = manifestService.CreateApplication(newRequestContext(), w, req) - - var appResp router.CreateApplicationResponse - err = json.NewDecoder(w.Body).Decode(&appResp) - assert.NoError(tt, err) - - assert.NotEmpty(tt, appResp.Application) - assert.Equal(tt, appResp.Application.Application.ManifestID, resp.Manifest.ID) - - }) - - t.Run("Test Get Application By ID and Get Applications", func(tt *testing.T) { - bolt, err := storage.NewBoltDB() - - // remove the db file after the test - tt.Cleanup(func() { - _ = bolt.Close() - _ = os.Remove(storage.DBFile) - }) - - manifestService := newManifestService(tt, bolt) - - w := httptest.NewRecorder() - - // get a application that doesn't exit - req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/applications/bad", nil) - err = manifestService.GetApplication(newRequestContext(), w, req) - assert.Error(tt, err) - assert.Contains(tt, err.Error(), "cannot get application without ID parameter") - - // reset recorder between calls - w.Flush() - - // good manifest 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) - - // good application request - createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) - - applicationRequestValue := newRequestValue(tt, createApplicationRequest) - req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) - err = manifestService.CreateApplication(newRequestContext(), w, req) - - var appResp router.CreateApplicationResponse - err = json.NewDecoder(w.Body).Decode(&appResp) - assert.NoError(tt, err) - - // get application by id - req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", appResp.Application.Application.ID), nil) - err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": appResp.Application.Application.ID}), w, req) - assert.NoError(tt, err) - - var getApplicationResp router.GetApplicationResponse - err = json.NewDecoder(w.Body).Decode(&getApplicationResp) - assert.NoError(tt, err) - assert.NotEmpty(tt, getApplicationResp) - assert.Equal(tt, resp.Manifest.ID, getApplicationResp.Application.Application.ManifestID) - - // good application request #2 - createApplicationRequest = getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) - - applicationRequestValue = newRequestValue(tt, createApplicationRequest) - req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) - err = manifestService.CreateApplication(newRequestContext(), w, req) - - var appRespTwo router.CreateApplicationResponse - err = json.NewDecoder(w.Body).Decode(&appRespTwo) - assert.NoError(tt, err) - - req = httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) - err = manifestService.GetApplications(newRequestContext(), w, req) - - var getApplicationsResp router.GetApplicationsResponse - err = json.NewDecoder(w.Body).Decode(&getApplicationsResp) - assert.NoError(tt, err) - assert.NotEmpty(tt, getApplicationsResp) - - assert.Len(tt, getApplicationsResp.Applications, 2) - }) - - t.Run("Test Delete Application", func(tt *testing.T) { - bolt, err := storage.NewBoltDB() - - // remove the db file after the test - tt.Cleanup(func() { - _ = bolt.Close() - _ = os.Remove(storage.DBFile) - }) - - manifestService := newManifestService(tt, bolt) - - // good manifest request - createManifestRequest := getValidManifestRequest() - - requestValue := newRequestValue(tt, createManifestRequest) - req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests", requestValue) - w := httptest.NewRecorder() - 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) - - // good application request - createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) - - applicationRequestValue := newRequestValue(tt, createApplicationRequest) - req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) - err = manifestService.CreateApplication(newRequestContext(), w, req) - - var appResp router.CreateApplicationResponse - err = json.NewDecoder(w.Body).Decode(&appResp) - assert.NoError(tt, err) - - // get the application - req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", appResp.Application.Application.ID), nil) - err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": appResp.Application.Application.ID}), w, req) - assert.NoError(tt, err) - - var getApplicationResp router.GetApplicationResponse - err = json.NewDecoder(w.Body).Decode(&getApplicationResp) - assert.NoError(tt, err) - assert.NotEmpty(tt, getApplicationResp) - assert.Equal(tt, resp.Manifest.ID, getApplicationResp.Application.Application.ManifestID) - - // delete the application - req = httptest.NewRequest(http.MethodDelete, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", getApplicationResp.Application.Application.ID), nil) - err = manifestService.DeleteApplication(newRequestContextWithParams(map[string]string{"id": getApplicationResp.Application.Application.ID}), w, req) - assert.NoError(tt, err) - - w.Flush() - - // get it back - req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", appResp.Application.Application.ID), nil) - err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": appResp.Application.Application.ID}), w, req) - assert.Error(tt, err) - assert.Contains(tt, err.Error(), fmt.Sprintf("could not get application with id: %s", appResp.Application.Application.ID)) - }) + // + //t.Run("Test Submit Application", func(tt *testing.T) { + // bolt, err := storage.NewBoltDB() + // + // // remove the db file after the test + // tt.Cleanup(func() { + // _ = bolt.Close() + // _ = os.Remove(storage.DBFile) + // }) + // + // manifestService := newManifestService(tt, bolt) + // + // // missing required field: OutputDescriptors + // badManifestRequest := router.SubmitApplicationRequest{ + // ManifestID: "id123", + // } + // + // badRequestValue := newRequestValue(tt, badManifestRequest) + // req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", badRequestValue) + // w := httptest.NewRecorder() + // + // err = manifestService.SubmitApplication(newRequestContext(), w, req) + // assert.Error(tt, err) + // assert.Contains(tt, err.Error(), "invalid submit application request") + // + // // reset the http recorder + // w.Flush() + // + // // 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) + // + // assert.NotEmpty(tt, resp.Manifest) + // assert.Equal(tt, resp.Manifest.Issuer.ID, "did:abc:123") + // + // // good application request + // createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) + // + // applicationRequestValue := newRequestValue(tt, createApplicationRequest) + // req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + // err = manifestService.SubmitApplication(newRequestContext(), w, req) + // + // var appResp router.SubmitApplicationResponse + // err = json.NewDecoder(w.Body).Decode(&appResp) + // assert.NoError(tt, err) + // + // assert.NotEmpty(tt, appResp.Response) + // assert.Equal(tt, appResp.Response.ManifestID, resp.Manifest.ID) + // + //}) + // + //t.Run("Test Get Application By ID and Get Applications", func(tt *testing.T) { + // bolt, err := storage.NewBoltDB() + // + // // remove the db file after the test + // tt.Cleanup(func() { + // _ = bolt.Close() + // _ = os.Remove(storage.DBFile) + // }) + // + // manifestService := newManifestService(tt, bolt) + // + // w := httptest.NewRecorder() + // + // // get a application that doesn't exit + // req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/applications/bad", nil) + // err = manifestService.GetApplication(newRequestContext(), w, req) + // assert.Error(tt, err) + // assert.Contains(tt, err.Error(), "cannot get application without ID parameter") + // + // // reset recorder between calls + // w.Flush() + // + // // good manifest 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) + // + // // good application request + // createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) + // + // applicationRequestValue := newRequestValue(tt, createApplicationRequest) + // req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + // err = manifestService.SubmitApplication(newRequestContext(), w, req) + // + // var appResp router.SubmitApplicationResponse + // err = json.NewDecoder(w.Body).Decode(&appResp) + // assert.NoError(tt, err) + // + // // get application by id + // req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", appResp.Response.ID), nil) + // err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": appResp.Response.ID}), w, req) + // assert.NoError(tt, err) + // + // var getApplicationResp router.GetApplicationResponse + // err = json.NewDecoder(w.Body).Decode(&getApplicationResp) + // assert.NoError(tt, err) + // assert.NotEmpty(tt, getApplicationResp) + // assert.Equal(tt, resp.Manifest.ID, getApplicationResp.Application.ManifestID) + // + // // good application request #2 + // createApplicationRequest = getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) + // + // applicationRequestValue = newRequestValue(tt, createApplicationRequest) + // req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + // err = manifestService.SubmitApplication(newRequestContext(), w, req) + // + // var appRespTwo router.SubmitApplicationResponse + // err = json.NewDecoder(w.Body).Decode(&appRespTwo) + // assert.NoError(tt, err) + // + // req = httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + // err = manifestService.GetApplications(newRequestContext(), w, req) + // + // var getApplicationsResp router.GetApplicationsResponse + // err = json.NewDecoder(w.Body).Decode(&getApplicationsResp) + // assert.NoError(tt, err) + // assert.NotEmpty(tt, getApplicationsResp) + // + // assert.Len(tt, getApplicationsResp.Applications, 2) + //}) + // + //t.Run("Test Delete Application", func(tt *testing.T) { + // bolt, err := storage.NewBoltDB() + // + // // remove the db file after the test + // tt.Cleanup(func() { + // _ = bolt.Close() + // _ = os.Remove(storage.DBFile) + // }) + // + // manifestService := newManifestService(tt, bolt) + // + // // good manifest request + // createManifestRequest := getValidManifestRequest() + // + // requestValue := newRequestValue(tt, createManifestRequest) + // req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests", requestValue) + // w := httptest.NewRecorder() + // 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) + // + // // good application request + // createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) + // + // applicationRequestValue := newRequestValue(tt, createApplicationRequest) + // req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + // err = manifestService.SubmitApplication(newRequestContext(), w, req) + // + // var appResp router.SubmitApplicationResponse + // err = json.NewDecoder(w.Body).Decode(&appResp) + // assert.NoError(tt, err) + // + // // get the application + // req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", appResp.Response.ID), nil) + // err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": appResp.Response.ID}), w, req) + // assert.NoError(tt, err) + // + // var getApplicationResp router.GetApplicationResponse + // err = json.NewDecoder(w.Body).Decode(&getApplicationResp) + // assert.NoError(tt, err) + // assert.NotEmpty(tt, getApplicationResp) + // assert.Equal(tt, resp.Manifest.ID, getApplicationResp.Application.ManifestID) + // + // // delete the application + // req = httptest.NewRequest(http.MethodDelete, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", getApplicationResp.Application.ID), nil) + // err = manifestService.DeleteApplication(newRequestContextWithParams(map[string]string{"id": getApplicationResp.Application.ID}), w, req) + // assert.NoError(tt, err) + // + // w.Flush() + // + // // get it back + // req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", appResp.Response.ID), nil) + // err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": appResp.Response.ID}), w, req) + // assert.Error(tt, err) + // assert.Contains(tt, err.Error(), fmt.Sprintf("could not get application with id: %s", appResp.Response.ID)) + //}) } func newManifestService(t *testing.T, bolt *storage.BoltDB) *router.ManifestRouter { @@ -1313,9 +1313,9 @@ func getValidManifestRequest() router.CreateManifestRequest { return createManifestRequest } -func getValidApplicationRequest(manifestId string, submissionDescriptorId string) router.CreateApplicationRequest { +func getValidApplicationRequest(manifestId string, submissionDescriptorId string) router.SubmitApplicationRequest { - createApplicationRequest := router.CreateApplicationRequest{ + createApplicationRequest := router.SubmitApplicationRequest{ ManifestID: manifestId, PresentationSubmission: exchange.PresentationSubmission{ diff --git a/pkg/service/manifest/manifest.go b/pkg/service/manifest/manifest.go index e9b9858c6..4b7f5864f 100644 --- a/pkg/service/manifest/manifest.go +++ b/pkg/service/manifest/manifest.go @@ -3,6 +3,7 @@ package manifest import ( "encoding/json" "fmt" + "github.com/TBD54566975/ssi-sdk/credential" "github.com/TBD54566975/ssi-sdk/credential/exchange" "github.com/TBD54566975/ssi-sdk/credential/manifest" "github.com/TBD54566975/ssi-sdk/crypto" @@ -12,6 +13,7 @@ import ( "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" + "time" ) type Service struct { @@ -49,7 +51,6 @@ func NewManifestService(config config.ManifestServiceConfig, s storage.ServiceSt }, nil } -// Manifests func (s Service) CreateManifest(request CreateManifestRequest) (*CreateManifestResponse, error) { logrus.Debugf("creating manifest: %+v", request) @@ -70,15 +71,14 @@ func (s Service) CreateManifest(request CreateManifestRequest) (*CreateManifestR } // parse OutputDescriptors - odJsonString, err := json.Marshal(request.OutputDescriptors) + odJSONBytes, err := json.Marshal(request.OutputDescriptors) if err != nil { errMsg := "could not marshal request output descriptors" return nil, util.LoggingErrorMsg(err, errMsg) } - od := []manifest.OutputDescriptor{} - err = json.Unmarshal(odJsonString, &od) - if err != nil { + var od []manifest.OutputDescriptor + if err = json.Unmarshal(odJSONBytes, &od); err != nil { errMsg := "could not unmarshal output descriptors" return nil, util.LoggingErrorMsg(err, errMsg) } @@ -86,18 +86,18 @@ func (s Service) CreateManifest(request CreateManifestRequest) (*CreateManifestR builder.SetOutputDescriptors(od) // parse PresentationDefinition - pdJsonString, err := json.Marshal(request.PresentationDefinition) + pdJSONBytes, err := json.Marshal(request.PresentationDefinition) if err != nil { errMsg := "could not marshal request presentation definition" return nil, util.LoggingErrorMsg(err, errMsg) } - pd := exchange.PresentationDefinition{} - err = json.Unmarshal(pdJsonString, &pd) - if err != nil { + var pd exchange.PresentationDefinition + if err = json.Unmarshal(pdJSONBytes, &pd); err != nil { errMsg := "could not unmarshal presentation definition" return nil, util.LoggingErrorMsg(err, errMsg) } + builder.SetPresentationDefinition(pd) mfst, err := builder.Build() @@ -165,28 +165,20 @@ func (s Service) DeleteManifest(request DeleteManifestRequest) error { return nil } -// TODO: (Neal) Add entire validation framework in place of these validation checks -func isValidApplication(s Service, request CreateApplicationRequest, ps exchange.PresentationSubmission) error { - - gotManifest, err := s.storage.GetManifest(request.ManifestID) - - if err != nil { - return util.LoggingErrorMsg(err, "problem with retrieving manifest during application validation") - } - +// TODO: (Neal) Add entire validation framework in place of these validation checks - https://github.com/TBD54566975/ssi-service/issues/95 +func isValidApplication(gotManifest *manifeststorage.StoredManifest, request SubmitApplicationRequest, ps exchange.PresentationSubmission) error { if gotManifest == nil { return util.LoggingNewError(fmt.Sprintf("application is not valid. A manifest does not exist with id: %s", request.ManifestID)) } inputDescriptors := gotManifest.Manifest.PresentationDefinition.InputDescriptors - inputDescriptorIds := map[string]bool{} - + inputDescriptorIDs := make(map[string]bool) for _, inputDescriptor := range inputDescriptors { - inputDescriptorIds[inputDescriptor.ID] = true + inputDescriptorIDs[inputDescriptor.ID] = true } for _, submissionDescriptor := range ps.DescriptorMap { - if inputDescriptorIds[submissionDescriptor.ID] != true { + if inputDescriptorIDs[submissionDescriptor.ID] != true { return util.LoggingNewError("application is not valid. The submission descriptor ids do not match the input descriptor ids") } } @@ -194,25 +186,28 @@ func isValidApplication(s Service, request CreateApplicationRequest, ps exchange return nil } -// Applications -func (s Service) CreateApplication(request CreateApplicationRequest) (*CreateApplicationResponse, error) { +func (s Service) SubmitApplication(request SubmitApplicationRequest) (*SubmitApplicationResponse, error) { + + gotManifest, err := s.storage.GetManifest(request.ManifestID) + if err != nil { + return nil, util.LoggingErrorMsg(err, "problem with retrieving manifest during application validation") + } // parse OutputDescriptors - psJsonString, err := json.Marshal(request.PresentationSubmission) + psJSONBytes, err := json.Marshal(request.PresentationSubmission) if err != nil { errMsg := "could not marshal request presentation submission" return nil, util.LoggingErrorMsg(err, errMsg) } - ps := exchange.PresentationSubmission{} - err = json.Unmarshal(psJsonString, &ps) - if err != nil { + var ps exchange.PresentationSubmission + if err = json.Unmarshal(psJSONBytes, &ps); err != nil { errMsg := "could not unmarshal presentation submission" return nil, util.LoggingErrorMsg(err, errMsg) } // validate - if err := isValidApplication(s, request, ps); err != nil { + if err := isValidApplication(gotManifest, request, ps); err != nil { errMsg := fmt.Sprintf("could not validate application") return nil, util.LoggingErrorMsg(err, errMsg) } @@ -238,7 +233,7 @@ func (s Service) CreateApplication(request CreateApplicationRequest) (*CreateApp // store the application storageRequest := manifeststorage.StoredApplication{ - ID: credApp.Application.ID, + ID: credApp.ID, Application: *credApp, ManifestID: request.ManifestID, } @@ -248,8 +243,47 @@ func (s Service) CreateApplication(request CreateApplicationRequest) (*CreateApp return nil, util.LoggingErrorMsg(err, errMsg) } - // return the result - response := CreateApplicationResponse{Application: *credApp} + // build the credential response + responseBuilder := manifest.NewCredentialResponseBuilder(request.ManifestID) + responseBuilder.SetApplicationID(credApp.ID) + responseBuilder.SetFulfillment(credApp.PresentationSubmission.DescriptorMap) + + credRes, err := responseBuilder.Build() + if err != nil { + errMsg := "could not build response" + return nil, util.LoggingErrorMsg(err, errMsg) + } + + // store the response + responseStorageRequest := manifeststorage.StoredResponse{ + ID: credRes.ID, + Response: *credRes, + ManifestID: request.ManifestID, + } + + if err := s.storage.StoreResponse(responseStorageRequest); err != nil { + errMsg := "could not store response" + return nil, util.LoggingErrorMsg(err, errMsg) + } + + credentialBuilder := credential.NewVerifiableCredentialBuilder() + credentialBuilder.SetIssuer(gotManifest.Issuer) + credentialBuilder.SetCredentialSubject(map[string]interface{}{ + "id": "test-vc-id", + "company": "Block", + "website": "https://block.xyz", + }) + credentialBuilder.SetIssuanceDate(time.Now().Format(time.RFC3339)) + + cred, err := credentialBuilder.Build() + if err != nil { + errMsg := "could not build credential" + return nil, util.LoggingErrorMsg(err, errMsg) + } + + // TODO: Store the credential? + + response := SubmitApplicationResponse{Response: *credRes, Credential: *cred} return &response, nil } @@ -257,13 +291,13 @@ func (s Service) GetApplication(request GetApplicationRequest) (*GetApplicationR logrus.Debugf("getting application: %s", request.ID) - gotCred, err := s.storage.GetApplication(request.ID) + gotApp, err := s.storage.GetApplication(request.ID) if err != nil { errMsg := fmt.Sprintf("could not get application: %s", request.ID) return nil, util.LoggingErrorMsg(err, errMsg) } - response := GetApplicationResponse{Application: gotCred.Application} + response := GetApplicationResponse{Application: gotApp.Application} return &response, nil } @@ -271,14 +305,14 @@ func (s Service) GetApplications() (*GetApplicationsResponse, error) { logrus.Debugf("getting application(s)") - gotCreds, err := s.storage.GetApplications() + gotApps, err := s.storage.GetApplications() if err != nil { errMsg := fmt.Sprintf("could not get application(s)") return nil, util.LoggingErrorMsg(err, errMsg) } var apps []manifest.CredentialApplication - for _, cred := range gotCreds { + for _, cred := range gotApps { apps = append(apps, cred.Application) } @@ -297,3 +331,48 @@ func (s Service) DeleteApplication(request DeleteApplicationRequest) error { return nil } + +func (s Service) GetResponse(request GetResponseRequest) (*GetResponseResponse, error) { + + logrus.Debugf("getting response: %s", request.ID) + + gotResponse, err := s.storage.GetResponse(request.ID) + if err != nil { + errMsg := fmt.Sprintf("could not get response: %s", request.ID) + return nil, util.LoggingErrorMsg(err, errMsg) + } + + response := GetResponseResponse{Response: gotResponse.Response} + return &response, nil +} + +func (s Service) GetResponses() (*GetResponsesResponse, error) { + + logrus.Debugf("getting response(s)") + + gotResponses, err := s.storage.GetResponses() + if err != nil { + errMsg := fmt.Sprintf("could not get response(s)") + return nil, util.LoggingErrorMsg(err, errMsg) + } + + var responses []manifest.CredentialResponse + for _, res := range gotResponses { + responses = append(responses, res.Response) + } + + response := GetResponsesResponse{Responses: responses} + return &response, nil +} + +func (s Service) DeleteResponse(request DeleteResponseRequest) error { + + logrus.Debugf("deleting response: %s", request.ID) + + if err := s.storage.DeleteResponse(request.ID); err != nil { + errMsg := fmt.Sprintf("could not delete response with id: %s", request.ID) + return util.LoggingErrorMsg(err, errMsg) + } + + return nil +} diff --git a/pkg/service/manifest/model.go b/pkg/service/manifest/model.go index d6dc4527c..da296b96c 100644 --- a/pkg/service/manifest/model.go +++ b/pkg/service/manifest/model.go @@ -1,8 +1,8 @@ package manifest import ( + "github.com/TBD54566975/ssi-sdk/credential" exchangesdk "github.com/TBD54566975/ssi-sdk/credential/exchange" - applicationsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" ) @@ -36,13 +36,14 @@ type DeleteManifestRequest struct { } // Application -type CreateApplicationRequest struct { +type SubmitApplicationRequest struct { ManifestID string PresentationSubmission exchangesdk.PresentationSubmission } -type CreateApplicationResponse struct { - Application applicationsdk.CredentialApplication +type SubmitApplicationResponse struct { + Response manifestsdk.CredentialResponse + Credential credential.VerifiableCredential } type GetApplicationRequest struct { @@ -50,13 +51,30 @@ type GetApplicationRequest struct { } type GetApplicationResponse struct { - Application applicationsdk.CredentialApplication + Application manifestsdk.CredentialApplication } type GetApplicationsResponse struct { - Applications []applicationsdk.CredentialApplication + Applications []manifestsdk.CredentialApplication } type DeleteApplicationRequest struct { ID string } + +// Response +type GetResponseRequest struct { + ID string +} + +type GetResponseResponse struct { + Response manifestsdk.CredentialResponse +} + +type GetResponsesResponse struct { + Responses []manifestsdk.CredentialResponse +} + +type DeleteResponseRequest struct { + ID string +} diff --git a/pkg/service/manifest/storage/bolt.go b/pkg/service/manifest/storage/bolt.go index 087b36104..da608801e 100644 --- a/pkg/service/manifest/storage/bolt.go +++ b/pkg/service/manifest/storage/bolt.go @@ -11,6 +11,7 @@ import ( const ( manifestNamespace = "manifest" applicationNamespace = "application" + responseNamespace = "response" ) type BoltManifestStorage struct { @@ -93,7 +94,7 @@ func (b BoltManifestStorage) DeleteManifest(id string) error { } func (b BoltManifestStorage) StoreApplication(application StoredApplication) error { - id := application.Application.Application.ID + id := application.Application.ID if id == "" { err := errors.New("could not store application without an ID") logrus.WithError(err).Error() @@ -143,9 +144,9 @@ func (b BoltManifestStorage) GetApplications() ([]StoredApplication, error) { } var stored []StoredApplication for _, applicationBytes := range gotApplications { - var nextapplication StoredApplication - if err := json.Unmarshal(applicationBytes, &nextapplication); err == nil { - stored = append(stored, nextapplication) + var nextApplication StoredApplication + if err := json.Unmarshal(applicationBytes, &nextApplication); err == nil { + stored = append(stored, nextApplication) } } return stored, nil @@ -159,3 +160,71 @@ func (b BoltManifestStorage) DeleteApplication(id string) error { } return nil } + +func (b BoltManifestStorage) StoreResponse(response StoredResponse) error { + id := response.Response.ID + if id == "" { + err := errors.New("could not store response without an ID") + logrus.WithError(err).Error() + return err + } + responseBytes, err := json.Marshal(response) + if err != nil { + errMsg := fmt.Sprintf("could not store response: %s", id) + logrus.WithError(err).Error(errMsg) + return errors.Wrapf(err, errMsg) + } + return b.db.Write(responseNamespace, id, responseBytes) +} + +func (b BoltManifestStorage) GetResponse(id string) (*StoredResponse, error) { + responseBytes, err := b.db.Read(responseNamespace, id) + if err != nil { + errMsg := fmt.Sprintf("could not get response: %s", id) + logrus.WithError(err).Error(errMsg) + return nil, errors.Wrapf(err, errMsg) + } + if len(responseBytes) == 0 { + err := fmt.Errorf("response not found with id: %s", id) + logrus.WithError(err).Error("could not get response from storage") + return nil, err + } + var stored StoredResponse + if err := json.Unmarshal(responseBytes, &stored); err != nil { + errMsg := fmt.Sprintf("could not unmarshal stored response: %s", id) + logrus.WithError(err).Error(errMsg) + return nil, errors.Wrapf(err, errMsg) + } + return &stored, nil +} + +// GetResponses attempts to get all stored responses. It will return those it can even if it has trouble with some. +func (b BoltManifestStorage) GetResponses() ([]StoredResponse, error) { + gotResponses, err := b.db.ReadAll(responseNamespace) + if err != nil { + errMsg := "could not get all responses" + logrus.WithError(err).Error(errMsg) + return nil, errors.Wrap(err, errMsg) + } + if len(gotResponses) == 0 { + logrus.Info("no responses to get") + return nil, nil + } + var stored []StoredResponse + for _, responseBytes := range gotResponses { + var nextResponse StoredResponse + if err := json.Unmarshal(responseBytes, &nextResponse); err == nil { + stored = append(stored, nextResponse) + } + } + return stored, nil +} + +func (b BoltManifestStorage) DeleteResponse(id string) error { + if err := b.db.Delete(responseNamespace, id); err != nil { + errMsg := fmt.Sprintf("could not delete response: %s", id) + logrus.WithError(err).Error(errMsg) + return errors.Wrapf(err, errMsg) + } + return nil +} diff --git a/pkg/service/manifest/storage/storage.go b/pkg/service/manifest/storage/storage.go index 192e4bce1..9a94780df 100644 --- a/pkg/service/manifest/storage/storage.go +++ b/pkg/service/manifest/storage/storage.go @@ -17,22 +17,33 @@ type StoredManifest struct { type StoredApplication struct { ID string `json:"id"` Application manifest.CredentialApplication `json:"application"` - ManifestID string `json:"manifest_id"` - Format string `json:"format"` + ManifestID string `json:"manifestId"` +} + +type StoredResponse struct { + ID string `json:"id"` + Response manifest.CredentialResponse `json:"response"` + ManifestID string `json:"manifestId"` } type Storage interface { - // Manifest + // Credential Manifest StoreManifest(manifest StoredManifest) error GetManifest(id string) (*StoredManifest, error) GetManifests() ([]StoredManifest, error) DeleteManifest(id string) error - // Application + // Credential Application StoreApplication(application StoredApplication) error GetApplication(id string) (*StoredApplication, error) GetApplications() ([]StoredApplication, error) DeleteApplication(id string) error + + // Credential Response + StoreResponse(response StoredResponse) error + GetResponse(id string) (*StoredResponse, error) + GetResponses() ([]StoredResponse, error) + DeleteResponse(id string) error } // NewManifestStorage finds the manifest storage impl for a given ServiceStorage value From a579079b31b6167ae155b58b68c635c72ac244e4 Mon Sep 17 00:00:00 2001 From: Neal Roessler Date: Fri, 16 Sep 2022 13:43:28 -0500 Subject: [PATCH 3/8] adding responses and return object is now {response, credentials} --- pkg/server/router/manifest.go | 12 +- pkg/server/router/manifest_test.go | 12 +- pkg/server/server_test.go | 415 +++++++++++++++-------------- pkg/service/manifest/manifest.go | 109 +++++--- pkg/service/manifest/model.go | 2 +- 5 files changed, 300 insertions(+), 250 deletions(-) diff --git a/pkg/server/router/manifest.go b/pkg/server/router/manifest.go index 96532683f..67418c9b3 100644 --- a/pkg/server/router/manifest.go +++ b/pkg/server/router/manifest.go @@ -198,8 +198,8 @@ func (c SubmitApplicationRequest) ToServiceRequest() manifest.SubmitApplicationR } type SubmitApplicationResponse struct { - Response manifestsdk.CredentialResponse `json:"response"` - Credential credential.VerifiableCredential `json:"credential"` + Response manifestsdk.CredentialResponse `json:"response"` + Credentials []credential.VerifiableCredential `json:"credentials"` } // SubmitApplication godoc @@ -229,7 +229,7 @@ func (mr ManifestRouter) SubmitApplication(ctx context.Context, w http.ResponseW return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError) } - resp := SubmitApplicationResponse{Response: submitApplicationResponse.Response, Credential: submitApplicationResponse.Credential} + resp := SubmitApplicationResponse{Response: submitApplicationResponse.Response, Credentials: submitApplicationResponse.Credential} return framework.Respond(ctx, w, resp, http.StatusCreated) } @@ -281,9 +281,6 @@ type GetApplicationsResponse struct { // @Tags ApplicationAPI // @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} GetApplicationsResponse // @Failure 400 {string} string "Bad request" // @Failure 500 {string} string "Internal server error" @@ -379,9 +376,6 @@ type GetResponsesResponse struct { // @Tags ResponseAPI // @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} GetResponsesResponse // @Failure 400 {string} string "Bad request" // @Failure 500 {string} string "Internal server error" diff --git a/pkg/server/router/manifest_test.go b/pkg/server/router/manifest_test.go index 5ee6f2cc6..63c7a784e 100644 --- a/pkg/server/router/manifest_test.go +++ b/pkg/server/router/manifest_test.go @@ -55,12 +55,12 @@ func TestManifestRouter(t *testing.T) { assert.NotEmpty(tt, createdManifest.Manifest) // good application request - //createApplicationRequest := getValidApplicationRequest(createdManifest.Manifest.ID, createManifestRequest.PresentationDefinition.InputDescriptors[0].ID) - // - //createdApplication, err := manifestService.SubmitApplication(createApplicationRequest) - //assert.NoError(tt, err) - //assert.NotEmpty(tt, createdManifest) - //assert.NotEmpty(tt, createdApplication.Response.ID) + createApplicationRequest := getValidApplicationRequest(createdManifest.Manifest.ID, createManifestRequest.PresentationDefinition.InputDescriptors[0].ID) + + createdApplication, err := manifestService.SubmitApplication(createApplicationRequest) + assert.NoError(tt, err) + assert.NotEmpty(tt, createdManifest) + assert.NotEmpty(tt, createdApplication.Response.ID) }) } diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 1a055e875..5246205b5 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -925,202 +925,225 @@ func TestManifestAPI(t *testing.T) { assert.Error(tt, err) assert.Contains(tt, err.Error(), fmt.Sprintf("could not get manifest with id: %s", resp.Manifest.ID)) }) - // - //t.Run("Test Submit Application", func(tt *testing.T) { - // bolt, err := storage.NewBoltDB() - // - // // remove the db file after the test - // tt.Cleanup(func() { - // _ = bolt.Close() - // _ = os.Remove(storage.DBFile) - // }) - // - // manifestService := newManifestService(tt, bolt) - // - // // missing required field: OutputDescriptors - // badManifestRequest := router.SubmitApplicationRequest{ - // ManifestID: "id123", - // } - // - // badRequestValue := newRequestValue(tt, badManifestRequest) - // req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", badRequestValue) - // w := httptest.NewRecorder() - // - // err = manifestService.SubmitApplication(newRequestContext(), w, req) - // assert.Error(tt, err) - // assert.Contains(tt, err.Error(), "invalid submit application request") - // - // // reset the http recorder - // w.Flush() - // - // // 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) - // - // assert.NotEmpty(tt, resp.Manifest) - // assert.Equal(tt, resp.Manifest.Issuer.ID, "did:abc:123") - // - // // good application request - // createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) - // - // applicationRequestValue := newRequestValue(tt, createApplicationRequest) - // req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) - // err = manifestService.SubmitApplication(newRequestContext(), w, req) - // - // var appResp router.SubmitApplicationResponse - // err = json.NewDecoder(w.Body).Decode(&appResp) - // assert.NoError(tt, err) - // - // assert.NotEmpty(tt, appResp.Response) - // assert.Equal(tt, appResp.Response.ManifestID, resp.Manifest.ID) - // - //}) - // - //t.Run("Test Get Application By ID and Get Applications", func(tt *testing.T) { - // bolt, err := storage.NewBoltDB() - // - // // remove the db file after the test - // tt.Cleanup(func() { - // _ = bolt.Close() - // _ = os.Remove(storage.DBFile) - // }) - // - // manifestService := newManifestService(tt, bolt) - // - // w := httptest.NewRecorder() - // - // // get a application that doesn't exit - // req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/applications/bad", nil) - // err = manifestService.GetApplication(newRequestContext(), w, req) - // assert.Error(tt, err) - // assert.Contains(tt, err.Error(), "cannot get application without ID parameter") - // - // // reset recorder between calls - // w.Flush() - // - // // good manifest 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) - // - // // good application request - // createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) - // - // applicationRequestValue := newRequestValue(tt, createApplicationRequest) - // req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) - // err = manifestService.SubmitApplication(newRequestContext(), w, req) - // - // var appResp router.SubmitApplicationResponse - // err = json.NewDecoder(w.Body).Decode(&appResp) - // assert.NoError(tt, err) - // - // // get application by id - // req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", appResp.Response.ID), nil) - // err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": appResp.Response.ID}), w, req) - // assert.NoError(tt, err) - // - // var getApplicationResp router.GetApplicationResponse - // err = json.NewDecoder(w.Body).Decode(&getApplicationResp) - // assert.NoError(tt, err) - // assert.NotEmpty(tt, getApplicationResp) - // assert.Equal(tt, resp.Manifest.ID, getApplicationResp.Application.ManifestID) - // - // // good application request #2 - // createApplicationRequest = getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) - // - // applicationRequestValue = newRequestValue(tt, createApplicationRequest) - // req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) - // err = manifestService.SubmitApplication(newRequestContext(), w, req) - // - // var appRespTwo router.SubmitApplicationResponse - // err = json.NewDecoder(w.Body).Decode(&appRespTwo) - // assert.NoError(tt, err) - // - // req = httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) - // err = manifestService.GetApplications(newRequestContext(), w, req) - // - // var getApplicationsResp router.GetApplicationsResponse - // err = json.NewDecoder(w.Body).Decode(&getApplicationsResp) - // assert.NoError(tt, err) - // assert.NotEmpty(tt, getApplicationsResp) - // - // assert.Len(tt, getApplicationsResp.Applications, 2) - //}) - // - //t.Run("Test Delete Application", func(tt *testing.T) { - // bolt, err := storage.NewBoltDB() - // - // // remove the db file after the test - // tt.Cleanup(func() { - // _ = bolt.Close() - // _ = os.Remove(storage.DBFile) - // }) - // - // manifestService := newManifestService(tt, bolt) - // - // // good manifest request - // createManifestRequest := getValidManifestRequest() - // - // requestValue := newRequestValue(tt, createManifestRequest) - // req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests", requestValue) - // w := httptest.NewRecorder() - // 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) - // - // // good application request - // createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) - // - // applicationRequestValue := newRequestValue(tt, createApplicationRequest) - // req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) - // err = manifestService.SubmitApplication(newRequestContext(), w, req) - // - // var appResp router.SubmitApplicationResponse - // err = json.NewDecoder(w.Body).Decode(&appResp) - // assert.NoError(tt, err) - // - // // get the application - // req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", appResp.Response.ID), nil) - // err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": appResp.Response.ID}), w, req) - // assert.NoError(tt, err) - // - // var getApplicationResp router.GetApplicationResponse - // err = json.NewDecoder(w.Body).Decode(&getApplicationResp) - // assert.NoError(tt, err) - // assert.NotEmpty(tt, getApplicationResp) - // assert.Equal(tt, resp.Manifest.ID, getApplicationResp.Application.ManifestID) - // - // // delete the application - // req = httptest.NewRequest(http.MethodDelete, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", getApplicationResp.Application.ID), nil) - // err = manifestService.DeleteApplication(newRequestContextWithParams(map[string]string{"id": getApplicationResp.Application.ID}), w, req) - // assert.NoError(tt, err) - // - // w.Flush() - // - // // get it back - // req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", appResp.Response.ID), nil) - // err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": appResp.Response.ID}), w, req) - // assert.Error(tt, err) - // assert.Contains(tt, err.Error(), fmt.Sprintf("could not get application with id: %s", appResp.Response.ID)) - //}) + + t.Run("Test Submit Application", func(tt *testing.T) { + bolt, err := storage.NewBoltDB() + + // remove the db file after the test + tt.Cleanup(func() { + _ = bolt.Close() + _ = os.Remove(storage.DBFile) + }) + + manifestService := newManifestService(tt, bolt) + + // missing required field: OutputDescriptors + badManifestRequest := router.SubmitApplicationRequest{ + ManifestID: "id123", + } + + badRequestValue := newRequestValue(tt, badManifestRequest) + req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", badRequestValue) + w := httptest.NewRecorder() + + err = manifestService.SubmitApplication(newRequestContext(), w, req) + assert.Error(tt, err) + assert.Contains(tt, err.Error(), "invalid submit application request") + + // reset the http recorder + w.Flush() + + // 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) + + assert.NotEmpty(tt, resp.Manifest) + assert.Equal(tt, resp.Manifest.Issuer.ID, "did:abc:123") + + // good application request + createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) + + applicationRequestValue := newRequestValue(tt, createApplicationRequest) + req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + err = manifestService.SubmitApplication(newRequestContext(), w, req) + + var appResp router.SubmitApplicationResponse + err = json.NewDecoder(w.Body).Decode(&appResp) + assert.NoError(tt, err) + + assert.NotEmpty(tt, appResp.Response) + assert.Equal(tt, appResp.Response.ManifestID, resp.Manifest.ID) + + }) + + t.Run("Test Get Application By ID and Get Applications", func(tt *testing.T) { + bolt, err := storage.NewBoltDB() + + // remove the db file after the test + tt.Cleanup(func() { + _ = bolt.Close() + _ = os.Remove(storage.DBFile) + }) + + manifestService := newManifestService(tt, bolt) + + w := httptest.NewRecorder() + + // get a application that doesn't exit + req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/applications/bad", nil) + err = manifestService.GetApplication(newRequestContext(), w, req) + assert.Error(tt, err) + assert.Contains(tt, err.Error(), "cannot get application without ID parameter") + + // reset recorder between calls + w.Flush() + + // good manifest 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) + + // good application request + createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) + + applicationRequestValue := newRequestValue(tt, createApplicationRequest) + req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + err = manifestService.SubmitApplication(newRequestContext(), w, req) + + var appResp router.SubmitApplicationResponse + err = json.NewDecoder(w.Body).Decode(&appResp) + assert.NoError(tt, err) + + // get response by id + req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/responses/%s", appResp.Response.ID), nil) + err = manifestService.GetResponse(newRequestContextWithParams(map[string]string{"id": appResp.Response.ID}), w, req) + assert.NoError(tt, err) + + var getResponseResponse router.GetResponseResponse + err = json.NewDecoder(w.Body).Decode(&getResponseResponse) + assert.NoError(tt, err) + assert.NotEmpty(tt, getResponseResponse) + assert.Equal(tt, appResp.Response.ID, getResponseResponse.Response.ID) + + // get all responses + req = httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/responses", nil) + err = manifestService.GetResponses(newRequestContext(), w, req) + + var getResponsesResp router.GetResponsesResponse + err = json.NewDecoder(w.Body).Decode(&getResponsesResp) + assert.NoError(tt, err) + assert.NotEmpty(tt, getResponsesResp) + + assert.Len(tt, getResponsesResp.Responses, 1) + + // get all applications + req = httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + err = manifestService.GetApplications(newRequestContext(), w, req) + + var getApplicationsResp router.GetApplicationsResponse + err = json.NewDecoder(w.Body).Decode(&getApplicationsResp) + assert.NoError(tt, err) + assert.NotEmpty(tt, getApplicationsResp) + + assert.Len(tt, getApplicationsResp.Applications, 1) + + // get application by id + req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", getApplicationsResp.Applications[0].ID), nil) + err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": getApplicationsResp.Applications[0].ID}), w, req) + assert.NoError(tt, err) + + var getApplicationResponse router.GetApplicationResponse + err = json.NewDecoder(w.Body).Decode(&getApplicationResponse) + assert.NoError(tt, err) + assert.NotEmpty(tt, getApplicationResponse) + assert.Equal(tt, getApplicationsResp.Applications[0].ID, getApplicationResponse.ID) + }) + + t.Run("Test Delete Application", func(tt *testing.T) { + bolt, err := storage.NewBoltDB() + + // remove the db file after the test + tt.Cleanup(func() { + _ = bolt.Close() + _ = os.Remove(storage.DBFile) + }) + + manifestService := newManifestService(tt, bolt) + + // good manifest request + createManifestRequest := getValidManifestRequest() + + requestValue := newRequestValue(tt, createManifestRequest) + req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests", requestValue) + w := httptest.NewRecorder() + 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) + + // good application request + createApplicationRequest := getValidApplicationRequest(resp.Manifest.ID, resp.Manifest.PresentationDefinition.InputDescriptors[0].ID) + + applicationRequestValue := newRequestValue(tt, createApplicationRequest) + req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + err = manifestService.SubmitApplication(newRequestContext(), w, req) + + var appResp router.SubmitApplicationResponse + err = json.NewDecoder(w.Body).Decode(&appResp) + assert.NoError(tt, err) + + // get all applications + req = httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/applications", applicationRequestValue) + err = manifestService.GetApplications(newRequestContext(), w, req) + + var getApplicationsResp router.GetApplicationsResponse + err = json.NewDecoder(w.Body).Decode(&getApplicationsResp) + assert.NoError(tt, err) + assert.NotEmpty(tt, getApplicationsResp) + + assert.Len(tt, getApplicationsResp.Applications, 1) + + // get the application + req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", getApplicationsResp.Applications[0].ID), nil) + err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": getApplicationsResp.Applications[0].ID}), w, req) + assert.NoError(tt, err) + + var getApplicationResp router.GetApplicationResponse + err = json.NewDecoder(w.Body).Decode(&getApplicationResp) + assert.NoError(tt, err) + assert.NotEmpty(tt, getApplicationResp) + assert.Equal(tt, resp.Manifest.ID, getApplicationResp.Application.ManifestID) + + // delete the application + req = httptest.NewRequest(http.MethodDelete, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", getApplicationResp.Application.ID), nil) + err = manifestService.DeleteApplication(newRequestContextWithParams(map[string]string{"id": getApplicationResp.Application.ID}), w, req) + assert.NoError(tt, err) + + w.Flush() + + // get it back + req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/applications/%s", appResp.Response.ID), nil) + err = manifestService.GetApplication(newRequestContextWithParams(map[string]string{"id": appResp.Response.ID}), w, req) + assert.Error(tt, err) + assert.Contains(tt, err.Error(), fmt.Sprintf("could not get application with id: %s", appResp.Response.ID)) + }) } func newManifestService(t *testing.T, bolt *storage.BoltDB) *router.ManifestRouter { diff --git a/pkg/service/manifest/manifest.go b/pkg/service/manifest/manifest.go index 4b7f5864f..057480120 100644 --- a/pkg/service/manifest/manifest.go +++ b/pkg/service/manifest/manifest.go @@ -10,6 +10,7 @@ import ( "github.com/sirupsen/logrus" "github.com/tbd54566975/ssi-service/config" "github.com/tbd54566975/ssi-service/internal/util" + credentialstorage "github.com/tbd54566975/ssi-service/pkg/service/credential/storage" "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" @@ -17,8 +18,9 @@ import ( ) type Service struct { - storage manifeststorage.Storage - config config.ManifestServiceConfig + manifestStorage manifeststorage.Storage + credentialStorage credentialstorage.Storage + config config.ManifestServiceConfig } func (s Service) Type() framework.Type { @@ -26,12 +28,20 @@ func (s Service) Type() framework.Type { } func (s Service) Status() framework.Status { - if s.storage == nil { + if s.manifestStorage == nil { return framework.Status{ Status: framework.StatusNotReady, - Message: "no storage", + Message: "no manifestStorage", } } + + if s.credentialStorage == nil { + return framework.Status{ + Status: framework.StatusNotReady, + Message: "no credentialStorage", + } + } + return framework.Status{Status: framework.StatusReady} } @@ -42,12 +52,19 @@ func (s Service) Config() config.ManifestServiceConfig { func NewManifestService(config config.ManifestServiceConfig, s storage.ServiceStorage) (*Service, error) { manifestStorage, err := manifeststorage.NewManifestStorage(s) if err != nil { - errMsg := "could not instantiate storage for the manifest service" + errMsg := "could not instantiate manifestStorage for the manifest service" + return nil, util.LoggingErrorMsg(err, errMsg) + } + + credentialStorage, err := credentialstorage.NewCredentialStorage(s) + if err != nil { + errMsg := "could not instantiate credentialStorage for the manifest service" return nil, util.LoggingErrorMsg(err, errMsg) } return &Service{ - storage: manifestStorage, - config: config, + manifestStorage: manifestStorage, + credentialStorage: credentialStorage, + config: config, }, nil } @@ -113,7 +130,7 @@ func (s Service) CreateManifest(request CreateManifestRequest) (*CreateManifestR Issuer: request.Issuer, } - if err := s.storage.StoreManifest(storageRequest); err != nil { + if err := s.manifestStorage.StoreManifest(storageRequest); err != nil { errMsg := "could not store manifest" return nil, util.LoggingErrorMsg(err, errMsg) } @@ -127,7 +144,7 @@ func (s Service) GetManifest(request GetManifestRequest) (*GetManifestResponse, logrus.Debugf("getting manifest: %s", request.ID) - gotManifest, err := s.storage.GetManifest(request.ID) + gotManifest, err := s.manifestStorage.GetManifest(request.ID) if err != nil { errMsg := fmt.Sprintf("could not get manifest: %s", request.ID) return nil, util.LoggingErrorMsg(err, errMsg) @@ -138,7 +155,7 @@ func (s Service) GetManifest(request GetManifestRequest) (*GetManifestResponse, } func (s Service) GetManifests() (*GetManifestsResponse, error) { - gotManifests, err := s.storage.GetManifests() + gotManifests, err := s.manifestStorage.GetManifests() if err != nil { errMsg := fmt.Sprintf("could not get manifests(s)") @@ -157,7 +174,7 @@ func (s Service) DeleteManifest(request DeleteManifestRequest) error { logrus.Debugf("deleting manifest: %s", request.ID) - if err := s.storage.DeleteManifest(request.ID); err != nil { + if err := s.manifestStorage.DeleteManifest(request.ID); err != nil { errMsg := fmt.Sprintf("could not delete manifest with id: %s", request.ID) return util.LoggingErrorMsg(err, errMsg) } @@ -188,7 +205,7 @@ func isValidApplication(gotManifest *manifeststorage.StoredManifest, request Sub func (s Service) SubmitApplication(request SubmitApplicationRequest) (*SubmitApplicationResponse, error) { - gotManifest, err := s.storage.GetManifest(request.ManifestID) + gotManifest, err := s.manifestStorage.GetManifest(request.ManifestID) if err != nil { return nil, util.LoggingErrorMsg(err, "problem with retrieving manifest during application validation") } @@ -238,7 +255,7 @@ func (s Service) SubmitApplication(request SubmitApplicationRequest) (*SubmitApp ManifestID: request.ManifestID, } - if err := s.storage.StoreApplication(storageRequest); err != nil { + if err := s.manifestStorage.StoreApplication(storageRequest); err != nil { errMsg := "could not store application" return nil, util.LoggingErrorMsg(err, errMsg) } @@ -261,29 +278,45 @@ func (s Service) SubmitApplication(request SubmitApplicationRequest) (*SubmitApp ManifestID: request.ManifestID, } - if err := s.storage.StoreResponse(responseStorageRequest); err != nil { - errMsg := "could not store response" - return nil, util.LoggingErrorMsg(err, errMsg) - } + var creds []credential.VerifiableCredential + for _, od := range gotManifest.Manifest.OutputDescriptors { - credentialBuilder := credential.NewVerifiableCredentialBuilder() - credentialBuilder.SetIssuer(gotManifest.Issuer) - credentialBuilder.SetCredentialSubject(map[string]interface{}{ - "id": "test-vc-id", - "company": "Block", - "website": "https://block.xyz", - }) - credentialBuilder.SetIssuanceDate(time.Now().Format(time.RFC3339)) + if err := s.manifestStorage.StoreResponse(responseStorageRequest); err != nil { + errMsg := "could not store response" + return nil, util.LoggingErrorMsg(err, errMsg) + } - cred, err := credentialBuilder.Build() - if err != nil { - errMsg := "could not build credential" - return nil, util.LoggingErrorMsg(err, errMsg) - } + credentialBuilder := credential.NewVerifiableCredentialBuilder() + credentialBuilder.SetIssuer(gotManifest.Manifest.Issuer.ID) + credentialBuilder.SetCredentialSubject(map[string]interface{}{ + "id": credApp.ID, // TODO: Needs to be DID of the application submitter. + }) + credentialBuilder.SetIssuanceDate(time.Now().Format(time.RFC3339)) + + cred, err := credentialBuilder.Build() + if err != nil { + errMsg := "could not build credential" + return nil, util.LoggingErrorMsg(err, errMsg) + } - // TODO: Store the credential? + credentialStorageRequest := credentialstorage.StoredCredential{ + ID: cred.ID, + Credential: *cred, + Issuer: gotManifest.Manifest.Issuer.ID, + Subject: credApp.ID, // TODO: Needs to be DID of the application submitter. + Schema: od.Schema, + IssuanceDate: time.Now().Format(time.RFC3339), + } + + if err := s.credentialStorage.StoreCredential(credentialStorageRequest); err != nil { + errMsg := "could not store credential" + return nil, util.LoggingErrorMsg(err, errMsg) + } + + creds = append(creds, *cred) + } - response := SubmitApplicationResponse{Response: *credRes, Credential: *cred} + response := SubmitApplicationResponse{Response: *credRes, Credential: creds} return &response, nil } @@ -291,7 +324,7 @@ func (s Service) GetApplication(request GetApplicationRequest) (*GetApplicationR logrus.Debugf("getting application: %s", request.ID) - gotApp, err := s.storage.GetApplication(request.ID) + gotApp, err := s.manifestStorage.GetApplication(request.ID) if err != nil { errMsg := fmt.Sprintf("could not get application: %s", request.ID) return nil, util.LoggingErrorMsg(err, errMsg) @@ -305,7 +338,7 @@ func (s Service) GetApplications() (*GetApplicationsResponse, error) { logrus.Debugf("getting application(s)") - gotApps, err := s.storage.GetApplications() + gotApps, err := s.manifestStorage.GetApplications() if err != nil { errMsg := fmt.Sprintf("could not get application(s)") return nil, util.LoggingErrorMsg(err, errMsg) @@ -324,7 +357,7 @@ func (s Service) DeleteApplication(request DeleteApplicationRequest) error { logrus.Debugf("deleting application: %s", request.ID) - if err := s.storage.DeleteApplication(request.ID); err != nil { + if err := s.manifestStorage.DeleteApplication(request.ID); err != nil { errMsg := fmt.Sprintf("could not delete application with id: %s", request.ID) return util.LoggingErrorMsg(err, errMsg) } @@ -336,7 +369,7 @@ func (s Service) GetResponse(request GetResponseRequest) (*GetResponseResponse, logrus.Debugf("getting response: %s", request.ID) - gotResponse, err := s.storage.GetResponse(request.ID) + gotResponse, err := s.manifestStorage.GetResponse(request.ID) if err != nil { errMsg := fmt.Sprintf("could not get response: %s", request.ID) return nil, util.LoggingErrorMsg(err, errMsg) @@ -350,7 +383,7 @@ func (s Service) GetResponses() (*GetResponsesResponse, error) { logrus.Debugf("getting response(s)") - gotResponses, err := s.storage.GetResponses() + gotResponses, err := s.manifestStorage.GetResponses() if err != nil { errMsg := fmt.Sprintf("could not get response(s)") return nil, util.LoggingErrorMsg(err, errMsg) @@ -369,7 +402,7 @@ func (s Service) DeleteResponse(request DeleteResponseRequest) error { logrus.Debugf("deleting response: %s", request.ID) - if err := s.storage.DeleteResponse(request.ID); err != nil { + if err := s.manifestStorage.DeleteResponse(request.ID); err != nil { errMsg := fmt.Sprintf("could not delete response with id: %s", request.ID) return util.LoggingErrorMsg(err, errMsg) } diff --git a/pkg/service/manifest/model.go b/pkg/service/manifest/model.go index da296b96c..25b03f9ef 100644 --- a/pkg/service/manifest/model.go +++ b/pkg/service/manifest/model.go @@ -43,7 +43,7 @@ type SubmitApplicationRequest struct { type SubmitApplicationResponse struct { Response manifestsdk.CredentialResponse - Credential credential.VerifiableCredential + Credential []credential.VerifiableCredential } type GetApplicationRequest struct { From c56739ed46f9cbe5e643e58e60560fc254e90a3f Mon Sep 17 00:00:00 2001 From: Neal Roessler Date: Fri, 16 Sep 2022 13:48:18 -0500 Subject: [PATCH 4/8] updating issue tag and variables --- pkg/server/router/manifest.go | 6 +++--- pkg/service/manifest/manifest.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/server/router/manifest.go b/pkg/server/router/manifest.go index 67418c9b3..4cb28da3c 100644 --- a/pkg/server/router/manifest.go +++ b/pkg/server/router/manifest.go @@ -190,10 +190,10 @@ type SubmitApplicationRequest struct { PresentationSubmission exchangensdk.PresentationSubmission `json:"presentationSubmission" validate:"required"` } -func (c SubmitApplicationRequest) ToServiceRequest() manifest.SubmitApplicationRequest { +func (sar SubmitApplicationRequest) ToServiceRequest() manifest.SubmitApplicationRequest { return manifest.SubmitApplicationRequest{ - PresentationSubmission: c.PresentationSubmission, - ManifestID: c.ManifestID, + PresentationSubmission: sar.PresentationSubmission, + ManifestID: sar.ManifestID, } } diff --git a/pkg/service/manifest/manifest.go b/pkg/service/manifest/manifest.go index 057480120..ada85263d 100644 --- a/pkg/service/manifest/manifest.go +++ b/pkg/service/manifest/manifest.go @@ -79,7 +79,7 @@ func (s Service) CreateManifest(request CreateManifestRequest) (*CreateManifestR return nil, util.LoggingErrorMsg(err, errMsg) } - // TODO: (Neal) Add dynamic claim formats + // TODO: (Neal) Add dynamic claim formats https://github.com/TBD54566975/ssi-service/issues/96 if err := builder.SetClaimFormat(exchange.ClaimFormat{ JWT: &exchange.JWTType{Alg: []crypto.SignatureAlgorithm{crypto.EdDSA}}, }); err != nil { From 399638be2cf9d0ed0d9d9b3b6cc41a2375c8628d Mon Sep 17 00:00:00 2001 From: Neal Roessler Date: Fri, 16 Sep 2022 13:57:27 -0500 Subject: [PATCH 5/8] remove context from cm --- pkg/server/router/manifest.go | 5 +---- pkg/server/server_test.go | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/pkg/server/router/manifest.go b/pkg/server/router/manifest.go index 4cb28da3c..3f0c3fcfb 100644 --- a/pkg/server/router/manifest.go +++ b/pkg/server/router/manifest.go @@ -35,9 +35,7 @@ func NewManifestRouter(s svcframework.Service) (*ManifestRouter, error) { } 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"` + Issuer string `json:"issuer" validate:"required"` OutputDescriptors []manifestsdk.OutputDescriptor `json:"outputDescriptors" validate:"required"` PresentationDefinition exchange.PresentationDefinition `json:"presentationDefinition" validate:"required"` } @@ -45,7 +43,6 @@ type CreateManifestRequest struct { func (c CreateManifestRequest) ToServiceRequest() manifest.CreateManifestRequest { return manifest.CreateManifestRequest{ Issuer: c.Issuer, - Context: c.Context, OutputDescriptors: c.OutputDescriptors, PresentationDefinition: c.PresentationDefinition, } diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 5246205b5..63d131075 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -1300,8 +1300,7 @@ func newRequestContextWithParams(params map[string]string) context.Context { func getValidManifestRequest() router.CreateManifestRequest { createManifestRequest := router.CreateManifestRequest{ - Issuer: "did:abc:123", - Context: "context123", + Issuer: "did:abc:123", PresentationDefinition: exchange.PresentationDefinition{ ID: "pres-def-id", InputDescriptors: []exchange.InputDescriptor{ From 7d5520aec05052b0bf984171702d6932d19d03b5 Mon Sep 17 00:00:00 2001 From: Neal Roessler Date: Fri, 16 Sep 2022 14:12:39 -0500 Subject: [PATCH 6/8] adding requesterDid to response --- pkg/server/router/manifest.go | 4 +++- pkg/server/router/manifest_test.go | 3 ++- pkg/server/server_test.go | 3 ++- pkg/service/manifest/manifest.go | 4 ++-- pkg/service/manifest/model.go | 1 + 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/server/router/manifest.go b/pkg/server/router/manifest.go index 3f0c3fcfb..85b9d13f8 100644 --- a/pkg/server/router/manifest.go +++ b/pkg/server/router/manifest.go @@ -184,13 +184,15 @@ func (mr ManifestRouter) DeleteManifest(ctx context.Context, w http.ResponseWrit type SubmitApplicationRequest struct { ManifestID string `json:"manifestId" validate:"required"` + RequesterDID string `json:"requesterDid" validate:"required"` PresentationSubmission exchangensdk.PresentationSubmission `json:"presentationSubmission" validate:"required"` } func (sar SubmitApplicationRequest) ToServiceRequest() manifest.SubmitApplicationRequest { return manifest.SubmitApplicationRequest{ - PresentationSubmission: sar.PresentationSubmission, ManifestID: sar.ManifestID, + RequesterDID: sar.RequesterDID, + PresentationSubmission: sar.PresentationSubmission, } } diff --git a/pkg/server/router/manifest_test.go b/pkg/server/router/manifest_test.go index 63c7a784e..e2581742a 100644 --- a/pkg/server/router/manifest_test.go +++ b/pkg/server/router/manifest_test.go @@ -107,7 +107,8 @@ func getValidApplicationRequest(manifestId string, submissionDescriptorId string createApplicationRequest := manifest.SubmitApplicationRequest{ - ManifestID: manifestId, + ManifestID: manifestId, + RequesterDID: "did:user:123", PresentationSubmission: exchange.PresentationSubmission{ ID: "psid", DefinitionID: "definitionId", diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 63d131075..afc60b104 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -1339,7 +1339,8 @@ func getValidApplicationRequest(manifestId string, submissionDescriptorId string createApplicationRequest := router.SubmitApplicationRequest{ - ManifestID: manifestId, + ManifestID: manifestId, + RequesterDID: "did:user:123", PresentationSubmission: exchange.PresentationSubmission{ ID: "psid", DefinitionID: "definitionId", diff --git a/pkg/service/manifest/manifest.go b/pkg/service/manifest/manifest.go index ada85263d..6d67fd0d9 100644 --- a/pkg/service/manifest/manifest.go +++ b/pkg/service/manifest/manifest.go @@ -289,7 +289,7 @@ func (s Service) SubmitApplication(request SubmitApplicationRequest) (*SubmitApp credentialBuilder := credential.NewVerifiableCredentialBuilder() credentialBuilder.SetIssuer(gotManifest.Manifest.Issuer.ID) credentialBuilder.SetCredentialSubject(map[string]interface{}{ - "id": credApp.ID, // TODO: Needs to be DID of the application submitter. + "id": request.RequesterDID, }) credentialBuilder.SetIssuanceDate(time.Now().Format(time.RFC3339)) @@ -303,7 +303,7 @@ func (s Service) SubmitApplication(request SubmitApplicationRequest) (*SubmitApp ID: cred.ID, Credential: *cred, Issuer: gotManifest.Manifest.Issuer.ID, - Subject: credApp.ID, // TODO: Needs to be DID of the application submitter. + Subject: request.RequesterDID, Schema: od.Schema, IssuanceDate: time.Now().Format(time.RFC3339), } diff --git a/pkg/service/manifest/model.go b/pkg/service/manifest/model.go index 25b03f9ef..a94d2b593 100644 --- a/pkg/service/manifest/model.go +++ b/pkg/service/manifest/model.go @@ -38,6 +38,7 @@ type DeleteManifestRequest struct { // Application type SubmitApplicationRequest struct { ManifestID string + RequesterDID string PresentationSubmission exchangesdk.PresentationSubmission } From 7b485c97ac623b10aa7e6bfe961463d9dc6fe570 Mon Sep 17 00:00:00 2001 From: Neal Roessler Date: Fri, 16 Sep 2022 14:20:50 -0500 Subject: [PATCH 7/8] cap to manifestID --- pkg/server/router/manifest_test.go | 4 ++-- pkg/server/server_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/server/router/manifest_test.go b/pkg/server/router/manifest_test.go index e2581742a..cdefdb4c2 100644 --- a/pkg/server/router/manifest_test.go +++ b/pkg/server/router/manifest_test.go @@ -103,11 +103,11 @@ func getValidManifestRequest() manifest.CreateManifestRequest { return createManifestRequest } -func getValidApplicationRequest(manifestId string, submissionDescriptorId string) manifest.SubmitApplicationRequest { +func getValidApplicationRequest(manifestID string, submissionDescriptorId string) manifest.SubmitApplicationRequest { createApplicationRequest := manifest.SubmitApplicationRequest{ - ManifestID: manifestId, + ManifestID: manifestID, RequesterDID: "did:user:123", PresentationSubmission: exchange.PresentationSubmission{ ID: "psid", diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index afc60b104..bb7ae34de 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -1335,11 +1335,11 @@ func getValidManifestRequest() router.CreateManifestRequest { return createManifestRequest } -func getValidApplicationRequest(manifestId string, submissionDescriptorId string) router.SubmitApplicationRequest { +func getValidApplicationRequest(manifestID string, submissionDescriptorId string) router.SubmitApplicationRequest { createApplicationRequest := router.SubmitApplicationRequest{ - ManifestID: manifestId, + ManifestID: manifestID, RequesterDID: "did:user:123", PresentationSubmission: exchange.PresentationSubmission{ ID: "psid", From 6be32332dc20700d3f3b749dee52f6dcc3f60de2 Mon Sep 17 00:00:00 2001 From: Neal Roessler Date: Fri, 16 Sep 2022 18:42:51 -0500 Subject: [PATCH 8/8] using top level manifest and application for request --- pkg/server/router/manifest.go | 21 ++---- pkg/server/router/manifest_test.go | 75 +++++++++++-------- pkg/server/server_test.go | 86 +++++++++++---------- pkg/service/manifest/manifest.go | 115 +++++------------------------ pkg/service/manifest/model.go | 12 +-- 5 files changed, 124 insertions(+), 185 deletions(-) diff --git a/pkg/server/router/manifest.go b/pkg/server/router/manifest.go index 85b9d13f8..c8ab63137 100644 --- a/pkg/server/router/manifest.go +++ b/pkg/server/router/manifest.go @@ -4,8 +4,6 @@ import ( "context" "fmt" "github.com/TBD54566975/ssi-sdk/credential" - "github.com/TBD54566975/ssi-sdk/credential/exchange" - exchangensdk "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" @@ -35,16 +33,12 @@ func NewManifestRouter(s svcframework.Service) (*ManifestRouter, error) { } type CreateManifestRequest struct { - Issuer string `json:"issuer" validate:"required"` - OutputDescriptors []manifestsdk.OutputDescriptor `json:"outputDescriptors" validate:"required"` - PresentationDefinition exchange.PresentationDefinition `json:"presentationDefinition" validate:"required"` + Manifest manifestsdk.CredentialManifest `json:"manifest" validate:"required"` } func (c CreateManifestRequest) ToServiceRequest() manifest.CreateManifestRequest { return manifest.CreateManifestRequest{ - Issuer: c.Issuer, - OutputDescriptors: c.OutputDescriptors, - PresentationDefinition: c.PresentationDefinition, + Manifest: c.Manifest, } } @@ -183,16 +177,15 @@ func (mr ManifestRouter) DeleteManifest(ctx context.Context, w http.ResponseWrit } type SubmitApplicationRequest struct { - ManifestID string `json:"manifestId" validate:"required"` - RequesterDID string `json:"requesterDid" validate:"required"` - PresentationSubmission exchangensdk.PresentationSubmission `json:"presentationSubmission" validate:"required"` + Application manifestsdk.CredentialApplication `json:"application" validate:"required"` + // Once we have JWT signed wrapper that can get the did this can be removed + RequesterDID string `json:"requesterDid" validate:"required"` } func (sar SubmitApplicationRequest) ToServiceRequest() manifest.SubmitApplicationRequest { return manifest.SubmitApplicationRequest{ - ManifestID: sar.ManifestID, - RequesterDID: sar.RequesterDID, - PresentationSubmission: sar.PresentationSubmission, + Application: sar.Application, + RequesterDID: sar.RequesterDID, } } diff --git a/pkg/server/router/manifest_test.go b/pkg/server/router/manifest_test.go index cdefdb4c2..c936bde69 100644 --- a/pkg/server/router/manifest_test.go +++ b/pkg/server/router/manifest_test.go @@ -3,6 +3,8 @@ package router import ( "github.com/TBD54566975/ssi-sdk/credential/exchange" manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" + "github.com/TBD54566975/ssi-sdk/crypto" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/tbd54566975/ssi-service/config" "github.com/tbd54566975/ssi-service/pkg/service/framework" @@ -55,7 +57,7 @@ func TestManifestRouter(t *testing.T) { assert.NotEmpty(tt, createdManifest.Manifest) // good application request - createApplicationRequest := getValidApplicationRequest(createdManifest.Manifest.ID, createManifestRequest.PresentationDefinition.InputDescriptors[0].ID) + createApplicationRequest := getValidApplicationRequest(createdManifest.Manifest.ID, createManifestRequest.Manifest.PresentationDefinition.InputDescriptors[0].ID) createdApplication, err := manifestService.SubmitApplication(createApplicationRequest) assert.NoError(tt, err) @@ -67,35 +69,40 @@ func TestManifestRouter(t *testing.T) { 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"}, + Manifest: 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", + 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", + }, }, }, } @@ -105,11 +112,14 @@ func getValidManifestRequest() manifest.CreateManifestRequest { func getValidApplicationRequest(manifestID string, submissionDescriptorId string) manifest.SubmitApplicationRequest { - createApplicationRequest := manifest.SubmitApplicationRequest{ - - ManifestID: manifestID, - RequesterDID: "did:user:123", - PresentationSubmission: exchange.PresentationSubmission{ + createApplication := manifestsdk.CredentialApplication{ + ID: uuid.New().String(), + SpecVersion: "https://identity.foundation/credential-manifest/spec/v1.0.0/", + ManifestID: manifestID, + Format: &exchange.ClaimFormat{ + JWT: &exchange.JWTType{Alg: []crypto.SignatureAlgorithm{crypto.EdDSA}}, + }, + PresentationSubmission: &exchange.PresentationSubmission{ ID: "psid", DefinitionID: "definitionId", DescriptorMap: []exchange.SubmissionDescriptor{ @@ -122,5 +132,10 @@ func getValidApplicationRequest(manifestID string, submissionDescriptorId string }, } + createApplicationRequest := manifest.SubmitApplicationRequest{ + Application: createApplication, + RequesterDID: "did:user:123", + } + return createApplicationRequest } diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index bb7ae34de..b9fccdde2 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -748,10 +748,8 @@ func TestManifestAPI(t *testing.T) { manifestService := newManifestService(tt, bolt) - // missing required field: OutputDescriptors - badManifestRequest := router.CreateManifestRequest{ - Issuer: "did:abc:123", - } + // missing required field: Manifest + badManifestRequest := router.CreateManifestRequest{} badRequestValue := newRequestValue(tt, badManifestRequest) req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests", badRequestValue) @@ -937,9 +935,9 @@ func TestManifestAPI(t *testing.T) { manifestService := newManifestService(tt, bolt) - // missing required field: OutputDescriptors + // missing required field: Application badManifestRequest := router.SubmitApplicationRequest{ - ManifestID: "id123", + RequesterDID: "id123", } badRequestValue := newRequestValue(tt, badManifestRequest) @@ -1298,36 +1296,42 @@ func newRequestContextWithParams(params map[string]string) context.Context { return httptreemux.AddParamsToContext(ctx, params) } -func getValidManifestRequest() router.CreateManifestRequest { - createManifestRequest := router.CreateManifestRequest{ - Issuer: "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"}, +func getValidManifestRequest() manifest.CreateManifestRequest { + createManifestRequest := manifest.CreateManifestRequest{ + Manifest: 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", + 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", + }, }, }, } @@ -1335,13 +1339,16 @@ func getValidManifestRequest() router.CreateManifestRequest { return createManifestRequest } -func getValidApplicationRequest(manifestID string, submissionDescriptorId string) router.SubmitApplicationRequest { - - createApplicationRequest := router.SubmitApplicationRequest{ +func getValidApplicationRequest(manifestID string, submissionDescriptorId string) manifest.SubmitApplicationRequest { - ManifestID: manifestID, - RequesterDID: "did:user:123", - PresentationSubmission: exchange.PresentationSubmission{ + createApplication := manifestsdk.CredentialApplication{ + ID: uuid.New().String(), + SpecVersion: "https://identity.foundation/credential-manifest/spec/v1.0.0/", + ManifestID: manifestID, + Format: &exchange.ClaimFormat{ + JWT: &exchange.JWTType{Alg: []crypto.SignatureAlgorithm{crypto.EdDSA}}, + }, + PresentationSubmission: &exchange.PresentationSubmission{ ID: "psid", DefinitionID: "definitionId", DescriptorMap: []exchange.SubmissionDescriptor{ @@ -1354,5 +1361,10 @@ func getValidApplicationRequest(manifestID string, submissionDescriptorId string }, } + createApplicationRequest := manifest.SubmitApplicationRequest{ + Application: createApplication, + RequesterDID: "did:user:123", + } + return createApplicationRequest } diff --git a/pkg/service/manifest/manifest.go b/pkg/service/manifest/manifest.go index 6d67fd0d9..5be797fba 100644 --- a/pkg/service/manifest/manifest.go +++ b/pkg/service/manifest/manifest.go @@ -1,12 +1,9 @@ package manifest import ( - "encoding/json" "fmt" "github.com/TBD54566975/ssi-sdk/credential" - "github.com/TBD54566975/ssi-sdk/credential/exchange" "github.com/TBD54566975/ssi-sdk/credential/manifest" - "github.com/TBD54566975/ssi-sdk/crypto" "github.com/sirupsen/logrus" "github.com/tbd54566975/ssi-service/config" "github.com/tbd54566975/ssi-service/internal/util" @@ -71,63 +68,17 @@ func NewManifestService(config config.ManifestServiceConfig, s storage.ServiceSt func (s Service) CreateManifest(request CreateManifestRequest) (*CreateManifestResponse, error) { logrus.Debugf("creating manifest: %+v", request) - builder := manifest.NewCredentialManifestBuilder() - issuer := manifest.Issuer{ID: request.Issuer, Name: request.Issuer} - - if err := builder.SetIssuer(issuer); err != nil { - errMsg := fmt.Sprintf("could not build manifest when setting issuer: %s", request.Issuer) - return nil, util.LoggingErrorMsg(err, errMsg) - } - - // TODO: (Neal) Add dynamic claim formats https://github.com/TBD54566975/ssi-service/issues/96 - if err := builder.SetClaimFormat(exchange.ClaimFormat{ - JWT: &exchange.JWTType{Alg: []crypto.SignatureAlgorithm{crypto.EdDSA}}, - }); err != nil { - errMsg := fmt.Sprintf("could not build manifest when setting claim format") - return nil, util.LoggingErrorMsg(err, errMsg) - } - - // parse OutputDescriptors - odJSONBytes, err := json.Marshal(request.OutputDescriptors) - if err != nil { - errMsg := "could not marshal request output descriptors" - return nil, util.LoggingErrorMsg(err, errMsg) - } - - var od []manifest.OutputDescriptor - if err = json.Unmarshal(odJSONBytes, &od); err != nil { - errMsg := "could not unmarshal output descriptors" - return nil, util.LoggingErrorMsg(err, errMsg) - } - - builder.SetOutputDescriptors(od) - - // parse PresentationDefinition - pdJSONBytes, err := json.Marshal(request.PresentationDefinition) - if err != nil { - errMsg := "could not marshal request presentation definition" - return nil, util.LoggingErrorMsg(err, errMsg) - } - - var pd exchange.PresentationDefinition - if err = json.Unmarshal(pdJSONBytes, &pd); err != nil { - errMsg := "could not unmarshal presentation definition" - return nil, util.LoggingErrorMsg(err, errMsg) - } - - builder.SetPresentationDefinition(pd) - - mfst, err := builder.Build() - if err != nil { - errMsg := "could not build manifest" + mfst := request.Manifest + if err := mfst.IsValid(); err != nil { + errMsg := "manifest is not valid" return nil, util.LoggingErrorMsg(err, errMsg) } // store the manifest storageRequest := manifeststorage.StoredManifest{ ID: mfst.ID, - Manifest: *mfst, - Issuer: request.Issuer, + Manifest: mfst, + Issuer: mfst.Issuer.ID, } if err := s.manifestStorage.StoreManifest(storageRequest); err != nil { @@ -136,7 +87,7 @@ func (s Service) CreateManifest(request CreateManifestRequest) (*CreateManifestR } // return the result - response := CreateManifestResponse{Manifest: *mfst} + response := CreateManifestResponse{Manifest: mfst} return &response, nil } @@ -183,9 +134,9 @@ func (s Service) DeleteManifest(request DeleteManifestRequest) error { } // TODO: (Neal) Add entire validation framework in place of these validation checks - https://github.com/TBD54566975/ssi-service/issues/95 -func isValidApplication(gotManifest *manifeststorage.StoredManifest, request SubmitApplicationRequest, ps exchange.PresentationSubmission) error { +func isValidApplication(gotManifest *manifeststorage.StoredManifest, application manifest.CredentialApplication) error { if gotManifest == nil { - return util.LoggingNewError(fmt.Sprintf("application is not valid. A manifest does not exist with id: %s", request.ManifestID)) + return util.LoggingNewError(fmt.Sprintf("application is not valid. A manifest does not exist with id: %s", application.ManifestID)) } inputDescriptors := gotManifest.Manifest.PresentationDefinition.InputDescriptors @@ -194,7 +145,7 @@ func isValidApplication(gotManifest *manifeststorage.StoredManifest, request Sub inputDescriptorIDs[inputDescriptor.ID] = true } - for _, submissionDescriptor := range ps.DescriptorMap { + for _, submissionDescriptor := range application.PresentationSubmission.DescriptorMap { if inputDescriptorIDs[submissionDescriptor.ID] != true { return util.LoggingNewError("application is not valid. The submission descriptor ids do not match the input descriptor ids") } @@ -204,55 +155,29 @@ func isValidApplication(gotManifest *manifeststorage.StoredManifest, request Sub } func (s Service) SubmitApplication(request SubmitApplicationRequest) (*SubmitApplicationResponse, error) { + credApp := request.Application - gotManifest, err := s.manifestStorage.GetManifest(request.ManifestID) - if err != nil { - return nil, util.LoggingErrorMsg(err, "problem with retrieving manifest during application validation") - } - - // parse OutputDescriptors - psJSONBytes, err := json.Marshal(request.PresentationSubmission) - if err != nil { - errMsg := "could not marshal request presentation submission" + if err := credApp.IsValid(); err != nil { + errMsg := "application is not valid" return nil, util.LoggingErrorMsg(err, errMsg) } - var ps exchange.PresentationSubmission - if err = json.Unmarshal(psJSONBytes, &ps); err != nil { - errMsg := "could not unmarshal presentation submission" - return nil, util.LoggingErrorMsg(err, errMsg) + gotManifest, err := s.manifestStorage.GetManifest(credApp.ManifestID) + if err != nil { + return nil, util.LoggingErrorMsg(err, "problem with retrieving manifest during application validation") } // validate - if err := isValidApplication(gotManifest, request, ps); err != nil { + if err := isValidApplication(gotManifest, credApp); err != nil { errMsg := fmt.Sprintf("could not validate application") return nil, util.LoggingErrorMsg(err, errMsg) } - // build credential application - builder := manifest.NewCredentialApplicationBuilder(request.ManifestID) - - // TODO: (Neal) Add dynamic claim formats - if err := builder.SetApplicationClaimFormat(exchange.ClaimFormat{ - JWT: &exchange.JWTType{Alg: []crypto.SignatureAlgorithm{crypto.EdDSA}}, - }); err != nil { - errMsg := fmt.Sprintf("could not build application when setting claim format") - return nil, util.LoggingErrorMsg(err, errMsg) - } - - builder.SetPresentationSubmission(ps) - - credApp, err := builder.Build() - if err != nil { - errMsg := "could not build application" - return nil, util.LoggingErrorMsg(err, errMsg) - } - // store the application storageRequest := manifeststorage.StoredApplication{ ID: credApp.ID, - Application: *credApp, - ManifestID: request.ManifestID, + Application: credApp, + ManifestID: request.Application.ManifestID, } if err := s.manifestStorage.StoreApplication(storageRequest); err != nil { @@ -261,7 +186,7 @@ func (s Service) SubmitApplication(request SubmitApplicationRequest) (*SubmitApp } // build the credential response - responseBuilder := manifest.NewCredentialResponseBuilder(request.ManifestID) + responseBuilder := manifest.NewCredentialResponseBuilder(request.Application.ManifestID) responseBuilder.SetApplicationID(credApp.ID) responseBuilder.SetFulfillment(credApp.PresentationSubmission.DescriptorMap) @@ -275,7 +200,7 @@ func (s Service) SubmitApplication(request SubmitApplicationRequest) (*SubmitApp responseStorageRequest := manifeststorage.StoredResponse{ ID: credRes.ID, Response: *credRes, - ManifestID: request.ManifestID, + ManifestID: request.Application.ManifestID, } var creds []credential.VerifiableCredential diff --git a/pkg/service/manifest/model.go b/pkg/service/manifest/model.go index a94d2b593..b8f971df7 100644 --- a/pkg/service/manifest/model.go +++ b/pkg/service/manifest/model.go @@ -2,17 +2,12 @@ package manifest import ( "github.com/TBD54566975/ssi-sdk/credential" - exchangesdk "github.com/TBD54566975/ssi-sdk/credential/exchange" manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest" ) // Manifest type CreateManifestRequest struct { - Issuer string - // A context is optional. If not present, we'll apply default, required context values. - Context string - OutputDescriptors []manifestsdk.OutputDescriptor - PresentationDefinition exchangesdk.PresentationDefinition + Manifest manifestsdk.CredentialManifest } type CreateManifestResponse struct { @@ -37,9 +32,8 @@ type DeleteManifestRequest struct { // Application type SubmitApplicationRequest struct { - ManifestID string - RequesterDID string - PresentationSubmission exchangesdk.PresentationSubmission + Application manifestsdk.CredentialApplication + RequesterDID string } type SubmitApplicationResponse struct {