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

Commit

Permalink
Submission implementation with test
Browse files Browse the repository at this point in the history
  • Loading branch information
andresuribe87 committed Nov 28, 2022
1 parent 90e137a commit 318ae8d
Show file tree
Hide file tree
Showing 13 changed files with 327 additions and 36 deletions.
12 changes: 0 additions & 12 deletions pkg/server/router/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package router
import (
"context"
"fmt"
"github.com/TBD54566975/ssi-sdk/credential/exchange"
"github.com/pkg/errors"
svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework"
"github.com/tbd54566975/ssi-service/pkg/service/operation"
Expand All @@ -25,17 +24,6 @@ func NewOperationRouter(s svcframework.Service) (*OperationRouter, error) {
return &OperationRouter{service: service}, nil
}

type Operation struct {
ID string `json:"id"`
Done bool `json:"bool"`
Result *OperationResult `json:"result"`
}

type OperationResult struct {
Error string `json:"error"`
Response exchange.PresentationSubmission `json:"response"`
}

// GetOperation godoc
// @Summary Get an operation
// @Description Get operation by its ID
Expand Down
57 changes: 56 additions & 1 deletion pkg/server/router/presentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import (
"context"
"fmt"
"github.com/TBD54566975/ssi-sdk/credential/exchange"
"github.com/TBD54566975/ssi-sdk/credential/signing"
"github.com/goccy/go-json"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
credint "github.com/tbd54566975/ssi-service/internal/credential"
"github.com/tbd54566975/ssi-service/internal/keyaccess"
"github.com/tbd54566975/ssi-service/internal/util"
"github.com/tbd54566975/ssi-service/pkg/server/framework"
svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework"
"github.com/tbd54566975/ssi-service/pkg/service/presentation"
Expand Down Expand Up @@ -179,6 +183,40 @@ type CreateSubmissionRequest struct {
SubmissionJWT keyaccess.JWT `json:"submissionJwt" validate:"required"`
}

func (r CreateSubmissionRequest) ToServiceRequest() (*presentation.CreateSubmissionRequest, error) {
sdkVp, err := signing.ParseVerifiablePresentationFromJWT(r.SubmissionJWT.String())
if err != nil {
return nil, errors.Wrap(err, "parsing presentation from jwt")
}
if err := sdkVp.IsValid(); err != nil {
return nil, errors.Wrap(err, "verifying vp validity")
}

submissionData, err := json.Marshal(sdkVp.PresentationSubmission)
if err != nil {
return nil, errors.Wrap(err, "marshalling presentation_submission")
}
var s exchange.PresentationSubmission
if err := json.Unmarshal(submissionData, &s); err != nil {
return nil, errors.Wrap(err, "unmarshalling presentation submission")
}
if err := s.IsValid(); err != nil {
return nil, errors.Wrap(err, "verifying submission validity")
}
sdkVp.PresentationSubmission = s

credContainers, err := credint.NewCredentialContainerFromArray(sdkVp.VerifiableCredential)
if err != nil {
return nil, errors.Wrap(err, "parsing verifiable credential array")
}

return &presentation.CreateSubmissionRequest{
Presentation: *sdkVp,
SubmissionJWT: r.SubmissionJWT,
Submission: s,
Credentials: credContainers}, nil
}

type Operation struct {
ID string `json:"id"`
Done bool `json:"done"`
Expand All @@ -202,7 +240,24 @@ type OperationResult struct {
// @Failure 500 {string} string "Internal server error"
// @Router /v1/presentations/submissions [put]
func (pr PresentationRouter) CreateSubmission(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
var resp Operation
var request CreateSubmissionRequest
if err := framework.Decode(r, &request); err != nil {
return framework.NewRequestError(util.LoggingErrorMsg(err, "invalid create submission request"), http.StatusBadRequest)
}

req, err := request.ToServiceRequest()
if err != nil {
return framework.NewRequestError(util.LoggingErrorMsg(err, "invalid create submission request"), http.StatusBadRequest)
}

operation, err := pr.service.CreateSubmission(*req)
if err != nil {
return framework.NewRequestError(util.LoggingErrorMsg(err, "cannot create submission"), http.StatusInternalServerError)
}

resp := Operation{
ID: operation.ID,
}
return framework.Respond(ctx, w, resp, http.StatusCreated)
}

Expand Down
6 changes: 5 additions & 1 deletion pkg/server/router/presentation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ func TestPresentationDefinitionService(t *testing.T) {
s, err := storage.NewStorage(storage.Bolt)
assert.NoError(t, err)

service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s)
keyStoreService := testKeyStoreService(t, s)
didService := testDIDService(t, s, keyStoreService)
schemaService := testSchemaService(t, s, keyStoreService, didService)

service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s, didService.GetResolver(), schemaService)
assert.NoError(t, err)

t.Run("Create returns the created definition", func(t *testing.T) {
Expand Down
6 changes: 3 additions & 3 deletions pkg/server/router/testutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/tbd54566975/ssi-service/pkg/storage"
)

func testKeyStoreService(t *testing.T, db *storage.BoltDB) *keystore.Service {
func testKeyStoreService(t *testing.T, db storage.ServiceStorage) *keystore.Service {
serviceConfig := config.KeyStoreServiceConfig{ServiceKeyPassword: "test-password"}
// create a keystore service
keystoreService, err := keystore.NewKeyStoreService(serviceConfig, db)
Expand All @@ -23,7 +23,7 @@ func testKeyStoreService(t *testing.T, db *storage.BoltDB) *keystore.Service {
return keystoreService
}

func testDIDService(t *testing.T, db *storage.BoltDB, keyStore *keystore.Service) *did.Service {
func testDIDService(t *testing.T, db storage.ServiceStorage, keyStore *keystore.Service) *did.Service {
serviceConfig := config.DIDServiceConfig{
BaseServiceConfig: &config.BaseServiceConfig{
Name: "did",
Expand All @@ -38,7 +38,7 @@ func testDIDService(t *testing.T, db *storage.BoltDB, keyStore *keystore.Service
return didService
}

func testSchemaService(t *testing.T, db *storage.BoltDB, keyStore *keystore.Service, did *did.Service) *schema.Service {
func testSchemaService(t *testing.T, db storage.ServiceStorage, keyStore *keystore.Service, did *did.Service) *schema.Service {
serviceConfig := config.SchemaServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "schema"}}
// create a schema service
schemaService, err := schema.NewSchemaService(serviceConfig, db, keyStore, did.GetResolver())
Expand Down
153 changes: 152 additions & 1 deletion pkg/server/server_presentation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package server

import (
"fmt"
"github.com/TBD54566975/ssi-sdk/credential"
"github.com/TBD54566975/ssi-sdk/credential/exchange"
"github.com/TBD54566975/ssi-sdk/credential/signing"
"github.com/TBD54566975/ssi-sdk/crypto"
"github.com/goccy/go-json"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tbd54566975/ssi-service/config"
"github.com/tbd54566975/ssi-service/internal/keyaccess"
"github.com/tbd54566975/ssi-service/pkg/server/router"
"github.com/tbd54566975/ssi-service/pkg/service/presentation"
"github.com/tbd54566975/ssi-service/pkg/storage"
Expand Down Expand Up @@ -40,7 +44,11 @@ func TestPresentationAPI(t *testing.T) {
s, err := storage.NewStorage(storage.Bolt)
assert.NoError(t, err)

service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s)
keyStoreService := testKeyStoreService(t, s)
didService := testDIDService(t, s, keyStoreService)
schemaService := testSchemaService(t, s, keyStoreService, didService)

service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s, didService.GetResolver(), schemaService)
assert.NoError(t, err)

pRouter, err := router.NewPresentationRouter(service)
Expand Down Expand Up @@ -136,4 +144,147 @@ func TestPresentationAPI(t *testing.T) {
assert.Error(t, pRouter.DeletePresentationDefinition(newRequestContext(), w, req))
w.Flush()
})

t.Run("full flow of presentation", func(t *testing.T) {

definition := createPresentationDefinition(t, pRouter)
vc := credential.VerifiableCredential{
Context: []string{credential.VerifiableCredentialsLinkedDataContext},
ID: "7035a7ec-66c8-4aec-9191-a34e8cf1e82b",
Type: []string{credential.VerifiablePresentationType},
Issuer: "did:key:z4oJ8bFEFv7E3omhuK5LrAtL29Nmd8heBey9HtJCSvodSb7nrfaMrd6zb7fjYSRxrfSgBSDeM6Bs59KRKFgXSDWJcfcjs",
IssuanceDate: "2022-11-07T21:28:57Z",
ExpirationDate: "2051-10-05T14:48:00.000Z",
CredentialStatus: nil,
CredentialSubject: credential.CredentialSubject{
"additionalName": "Mclovin",
"dateOfBirth": "1987-01-02",
"familyName": "Andres",
"givenName": "Uribe",
"id": "did:web:andresuribe.com",
},
CredentialSchema: nil,
RefreshService: nil,
TermsOfUse: nil,
Evidence: nil,
Proof: nil,
}

signer0 := getTestVectorKey0Signer(t)
vcData, err := signing.SignVerifiableCredentialJWT(signer0, vc)
assert.NoError(t, err)
ps := exchange.PresentationSubmission{
ID: "a30e3b91-fb77-4d22-95fa-871689c322e2",
DefinitionID: definition.PresentationDefinition.ID,
DescriptorMap: []exchange.SubmissionDescriptor{
{
ID: "wa_driver_license",
Format: string(exchange.JWTVPTarget),
Path: "$.verifiableCredential[0]",
PathNested: nil,
},
},
}
vp := credential.VerifiablePresentation{
Context: []string{credential.VerifiableCredentialsLinkedDataContext},
ID: "a9b575c7-bac2-47e7-a925-c432815ebb4c",
Holder: "did:key:z4oJ8eRi73fvkrXBgqTHZRTropESXLc7Vet8XpJrGUSBZAT2UHvQBYpBEPdAUiyKBi2XC2iFjgtn5Gw2Qd4WXHyj1LxjU",
Type: []string{credential.VerifiablePresentationType},
PresentationSubmission: ps,
VerifiableCredential: []interface{}{keyaccess.JWT(vcData)},
Proof: nil,
}

signer1 := getTestVectorKey1Signer(t)
signed, err := signing.SignVerifiablePresentationJWT(signer1, vp)
assert.NoError(t, err)

request := router.CreateSubmissionRequest{SubmissionJWT: keyaccess.JWT(signed)}

value := newRequestValue(t, request)
req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/presentations/submissions", value)
w := httptest.NewRecorder()

err = pRouter.CreateSubmission(newRequestContext(), w, req)

require.NoError(t, err)
var resp router.Operation
assert.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
assert.Contains(t, resp.ID, "presentations/submissions/")
assert.False(t, resp.Done)
assert.Zero(t, resp.Result)
})
}

func createPresentationDefinition(t *testing.T, pRouter *router.PresentationRouter) router.CreatePresentationDefinitionResponse {
request := router.CreatePresentationDefinitionRequest{
Name: "name",
Purpose: "purpose",
Format: nil,
SubmissionRequirements: nil,
InputDescriptors: []exchange.InputDescriptor{
{
ID: "wa_driver_license",
Name: "washington state business license",
Purpose: "some testing stuff",
Format: nil,
Constraints: &exchange.Constraints{
Fields: []exchange.Field{
{
ID: "date_of_birth",
Path: []string{
"$.credentialSubject.dateOfBirth",
"$.credentialSubject.dob",
"$.vc.credentialSubject.dateOfBirth",
"$.vc.credentialSubject.dob",
},
},
},
},
},
},
}
value := newRequestValue(t, request)
req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/presentations/definitions", value)
w := httptest.NewRecorder()

assert.NoError(t, pRouter.CreatePresentationDefinition(newRequestContext(), w, req))
var resp router.CreatePresentationDefinitionResponse
assert.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
return resp
}

func getTestVectorKey0Signer(t *testing.T) crypto.JWTSigner {
// The corresponding JWT is below:
// eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6a2V5Ono0b0o4YkZFRnY3RTNvbWh1SzVMckF0TDI5Tm1kOGhlQmV5OUh0SkNTdm9kU2I3bnJmYU1yZDZ6YjdmallTUnhyZlNnQlNEZU02QnM1OUtSS0ZnWFNEV0pjZmNqcyIsImp0aSI6IjcwMzVhN2VjLTY2YzgtNGFlYy05MTkxLWEzNGU4Y2YxZTgyYiIsIm5iZiI6MTY2Nzg1NjUzNywic3ViIjoiZGlkOmtleTp6NG9KOGVSaTczZnZrclhCZ3FUSFpSVHJvcEVTWExjN1ZldDhYcEpyR1VTQlpBVDJVSHZRQllwQkVQZEFVaXlLQmkyWEMyaUZqZ3RuNUd3MlFkNFdYSHlqMUx4alUiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJpZCI6IjcwMzVhN2VjLTY2YzgtNGFlYy05MTkxLWEzNGU4Y2YxZTgyYiIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmtleTp6NG9KOGJGRUZ2N0Uzb21odUs1THJBdEwyOU5tZDhoZUJleTlIdEpDU3ZvZFNiN25yZmFNcmQ2emI3ZmpZU1J4cmZTZ0JTRGVNNkJzNTlLUktGZ1hTRFdKY2ZjanMiLCJpc3N1YW5jZURhdGUiOiIyMDIyLTExLTA3VDIxOjI4OjU3WiIsImV4cGlyYXRpb25EYXRlIjoiMjA1MS0xMC0wNVQxNDo0ODowMC4wMDBaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiYWRkaXRpb25hbE5hbWUiOiJNY2xvdmluIiwiZGF0ZU9mQmlydGgiOiIxOTg3LTAxLTAyIiwiZmFtaWx5TmFtZSI6IkFuZHJlcyIsImdpdmVuTmFtZSI6IlVyaWJlIiwiaWQiOiJkaWQ6a2V5Ono0b0o4ZVJpNzNmdmtyWEJncVRIWlJUcm9wRVNYTGM3VmV0OFhwSnJHVVNCWkFUMlVIdlFCWXBCRVBkQVVpeUtCaTJYQzJpRmpndG41R3cyUWQ0V1hIeWoxTHhqVSJ9fX0.mtrK1nDLL1Ly6iPwIpgpMbLFtoHcH52OQLbBSF-jVK7UHFZdKb8v4e_27uKZO0uszRm11kRV1NnDxoRJNNjFbw

// AKA the issuers key
// AKA did:key:z4oJ8bFEFv7E3omhuK5LrAtL29Nmd8heBey9HtJCSvodSb7nrfaMrd6zb7fjYSRxrfSgBSDeM6Bs59KRKFgXSDWJcfcjs
knownJWK := crypto.PrivateKeyJWK{
KTY: "EC",
CRV: "P-256",
X: "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74",
Y: "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI",
D: "0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk",
}

signer, err := crypto.NewJWTSignerFromJWK(knownJWK.KID, knownJWK)
assert.NoError(t, err)
return *signer
}

func getTestVectorKey1Signer(t *testing.T) crypto.JWTSigner {
// AKA the submitter of the presentation
// AKA did:key:z4oJ8eRi73fvkrXBgqTHZRTropESXLc7Vet8XpJrGUSBZAT2UHvQBYpBEPdAUiyKBi2XC2iFjgtn5Gw2Qd4WXHyj1LxjU
knownJWK := crypto.PrivateKeyJWK{
KTY: "EC",
CRV: "P-256",
X: "6HEz8SLP7NgHPGp0bElryiD7u3_cO1EmX-ngsV_yLsI",
Y: "QlIYaYyDLxLkybDan9LOSkfGvjzZsrdgAb_nQr_Li5M",
D: "7m6c2Axy9OWi7-d9hFVhmMe22vQTfQDL_pG-3WFsjzc",
}

signer, err := crypto.NewJWTSignerFromJWK(knownJWK.KID, knownJWK)
assert.NoError(t, err)
return *signer
}
8 changes: 4 additions & 4 deletions pkg/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func testKeyStore(t *testing.T, bolt *storage.BoltDB) (*router.KeyStoreRouter, *
return keyStoreRouter, keyStoreService
}

func testKeyStoreService(t *testing.T, db *storage.BoltDB) *keystore.Service {
func testKeyStoreService(t *testing.T, db storage.ServiceStorage) *keystore.Service {
serviceConfig := config.KeyStoreServiceConfig{
BaseServiceConfig: &config.BaseServiceConfig{Name: "test-keystore"},
ServiceKeyPassword: "test-password",
Expand All @@ -208,7 +208,7 @@ func testKeyStoreService(t *testing.T, db *storage.BoltDB) *keystore.Service {
return keystoreService
}

func testDIDService(t *testing.T, bolt *storage.BoltDB, keyStore *keystore.Service) *did.Service {
func testDIDService(t *testing.T, bolt storage.ServiceStorage, keyStore *keystore.Service) *did.Service {
serviceConfig := config.DIDServiceConfig{
BaseServiceConfig: &config.BaseServiceConfig{Name: "test-did"},
Methods: []string{"key"},
Expand All @@ -232,7 +232,7 @@ func testDIDRouter(t *testing.T, bolt *storage.BoltDB, keyStore *keystore.Servic
return didRouter
}

func testSchemaService(t *testing.T, bolt *storage.BoltDB, keyStore *keystore.Service, did *did.Service) *schema.Service {
func testSchemaService(t *testing.T, bolt storage.ServiceStorage, keyStore *keystore.Service, did *did.Service) *schema.Service {
schemaService, err := schema.NewSchemaService(config.SchemaServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "test-schema"}}, bolt, keyStore, did.GetResolver())
require.NoError(t, err)
require.NotEmpty(t, schemaService)
Expand All @@ -249,7 +249,7 @@ func testSchemaRouter(t *testing.T, bolt *storage.BoltDB, keyStore *keystore.Ser
return schemaRouter
}

func testCredentialService(t *testing.T, db *storage.BoltDB, keyStore *keystore.Service, did *did.Service, schema *schema.Service) *credential.Service {
func testCredentialService(t *testing.T, db storage.ServiceStorage, keyStore *keystore.Service, did *did.Service, schema *schema.Service) *credential.Service {
serviceConfig := config.CredentialServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "credential"}}

// create a credential service
Expand Down
12 changes: 12 additions & 0 deletions pkg/service/operation/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package operation

type Result struct {
Error string `json:"error,omitempty"`
Response interface{} `json:"response,omitempty"`
}

type Operation struct {
ID string `json:"json"`
Done bool `json:"done"`
Result Result `json:"result,omitempty"`
}
8 changes: 7 additions & 1 deletion pkg/service/presentation/model.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package presentation

import (
credsdk "github.com/TBD54566975/ssi-sdk/credential"
"github.com/TBD54566975/ssi-sdk/credential/exchange"
"github.com/TBD54566975/ssi-sdk/util"
"github.com/tbd54566975/ssi-service/internal/credential"
"github.com/tbd54566975/ssi-service/internal/keyaccess"
)

type CreatePresentationDefinitionRequest struct {
Expand Down Expand Up @@ -31,7 +34,10 @@ type DeletePresentationDefinitionRequest struct {
}

type CreateSubmissionRequest struct {
Submission exchange.PresentationSubmission `json:"submission" validate:"required"`
Presentation credsdk.VerifiablePresentation `json:"presentation" validate:"required"`
SubmissionJWT keyaccess.JWT `json:"submissionJwt,omitempty" validate:"required"`
Submission exchange.PresentationSubmission `json:"submission" validate:"required"`
Credentials []credential.Container `json:"credentials,omitempty"`
}

func (csr CreateSubmissionRequest) IsValid() bool {
Expand Down
Loading

0 comments on commit 318ae8d

Please sign in to comment.