Skip to content

Commit

Permalink
Added batch update for statuses (#658)
Browse files Browse the repository at this point in the history
* Added batch update for statuses

* PR feedback.

---------

Co-authored-by: Gabe <[email protected]>
  • Loading branch information
andresuribe87 and decentralgabe authored Aug 17, 2023
1 parent 1955573 commit f1ac3f8
Show file tree
Hide file tree
Showing 16 changed files with 539 additions and 37 deletions.
4 changes: 3 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,10 @@ func (d *DIDServiceConfig) IsEmpty() bool {
}

type CredentialServiceConfig struct {
// BatchCreateMaxItems set's the maximum amount that can be.
// BatchCreateMaxItems set's the maximum amount of credentials that can be created in a single request.
BatchCreateMaxItems int `toml:"batch_create_max_items" conf:"default:100"`
// BatchUpdateStatusMaxItems set's the maximum amount of credentials statuses that can be updated in a single request.
BatchUpdateStatusMaxItems int `toml:"batch_update_status_max_items" conf:"default:100"`

// TODO(gabe) supported key and signature types
}
Expand Down
1 change: 1 addition & 0 deletions config/dev.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ batch_create_max_items = 100

[services.credential]
batch_create_max_items = 100
batch_update_status_max_items = 100

[services.webhook]
webhook_timeout = "10s"
1 change: 1 addition & 0 deletions config/kitchensink.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ batch_create_max_items = 100

[services.credential]
batch_create_max_items = 100
batch_update_status_max_items = 100

[services.webhook]
webhook_timeout = "10s"
1 change: 1 addition & 0 deletions config/prod.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ batch_create_max_items = 100

[services.credential]
batch_create_max_items = 100
batch_update_status_max_items = 100

[services.webhook]
webhook_timeout = "10s"
1 change: 1 addition & 0 deletions config/test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ batch_create_max_items = 100

[services.credential]
batch_create_max_items = 100
batch_update_status_max_items = 100

[services.webhook]
webhook_timeout = "10s"
76 changes: 76 additions & 0 deletions doc/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,19 @@ definitions:
description: Whether this credential is currently suspended.
type: boolean
type: object
github_com_tbd54566975_ssi-service_pkg_service_credential.Status:
properties:
id:
description: ID of the credentials whose status this object represents.
type: string
revoked:
type: boolean
suspended:
type: boolean
required:
- revoked
- suspended
type: object
github_com_tbd54566975_ssi-service_pkg_service_did.CreateIONDIDOptions:
properties:
jwsPublicKeys:
Expand Down Expand Up @@ -1088,6 +1101,24 @@ definitions:
$ref: '#/definitions/did.Document'
type: array
type: object
pkg_server_router.BatchUpdateCredentialStatusRequest:
properties:
requests:
description: Required. The list of update credential requests. Cannot be more
than the config value in `services.credentials.batch_update_status_max_items`.
items:
$ref: '#/definitions/pkg_server_router.SingleUpdateCredentialStatusRequest'
type: array
required:
- requests
type: object
pkg_server_router.BatchUpdateCredentialStatusResponse:
properties:
credentialStatuses:
items:
$ref: '#/definitions/github_com_tbd54566975_ssi-service_pkg_service_credential.Status'
type: array
type: object
pkg_server_router.CreateCredentialRequest:
properties:
'@context':
Expand Down Expand Up @@ -1943,6 +1974,21 @@ definitions:
id:
type: string
type: object
pkg_server_router.SingleUpdateCredentialStatusRequest:
properties:
id:
description: ID of the credential who's status should be updated.
type: string
revoked:
description: |-
The new revoked status of this credential. The status will be saved in the encodedList of the StatusList2021
credential associated with this VC.
type: boolean
suspended:
type: boolean
required:
- id
type: object
pkg_server_router.StateChange:
properties:
publicKeyIdsToRemove:
Expand Down Expand Up @@ -2612,6 +2658,36 @@ paths:
summary: Get a Credential Status List
tags:
- Credentials
/v1/credentials/status/batch:
put:
consumes:
- application/json
description: Updates the status all a batch of Verifiable Credentials.
parameters:
- description: request body
in: body
name: request
required: true
schema:
$ref: '#/definitions/pkg_server_router.BatchUpdateCredentialStatusRequest'
produces:
- application/json
responses:
"201":
description: Created
schema:
$ref: '#/definitions/pkg_server_router.BatchUpdateCredentialStatusResponse'
"400":
description: Bad request
schema:
type: string
"500":
description: Internal server error
schema:
type: string
summary: Batch Update a Verifiable Credential's status
tags:
- Credentials
/v1/credentials/verification:
put:
consumes:
Expand Down
23 changes: 23 additions & 0 deletions integration/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,29 @@ func BatchCreateVerifiableCredentials(credentialInput batchCredInputParams) (str
return output, nil
}

type batchUpdateStatusInputParams struct {
CredentialID0 string
Suspended0 bool
CredentialID1 string
Revoked1 bool
}

func BatchUpdateVerifiableCredentialStatuses(updateStatusInput batchUpdateStatusInputParams) (string, error) {
logrus.Println("\n\nCreate a verifiable credential")

updateStatusesJSON, err := resolveTemplate(updateStatusInput, "batch-update-credential-statuses-input.json")
if err != nil {
return "", err
}

output, err := put(endpoint+version+"credentials/status/batch", updateStatusesJSON)
if err != nil {
return "", errors.Wrap(err, "error writing batch update status")
}

return output, nil
}

func BatchCreate100VerifiableCredentials(credentialInput credInputParams) (string, error) {
logrus.Println("\n\nCreate a verifiable credential")

Expand Down
58 changes: 57 additions & 1 deletion integration/credential_manifest_integration_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package integration

import (
"strings"
"testing"

"github.com/TBD54566975/ssi-sdk/credential/parsing"
Expand Down Expand Up @@ -125,7 +126,7 @@ func TestBatchCreateCredentialsIntegration(t *testing.T) {
SchemaID: schemaID.(string),
SubjectID0: issuerDID.(string),
SubjectID1: issuerDID.(string),
Revocable0: false,
Suspendable0: true,
Revocable1: true,
})
assert.NoError(t, err)
Expand All @@ -138,6 +139,61 @@ func TestBatchCreateCredentialsIntegration(t *testing.T) {
credentialJWT1, err := getJSONElement(vcsOutput, "$.credentials[1].credentialJwt")
assert.NoError(t, err)
assert.NotEmpty(t, credentialJWT1)

credentialID0, err := getJSONElement(vcsOutput, "$.credentials[0].credential.id")
assert.NoError(t, err)

credentialID1, err := getJSONElement(vcsOutput, "$.credentials[1].credential.id")
assert.NoError(t, err)

SetValue(credentialManifestContext, "credentialID0", credentialID0)
SetValue(credentialManifestContext, "credentialID1", credentialID1)
}

func idFromURL(id string) string {
lastIdx := strings.LastIndex(id, "/")
return id[lastIdx+1:]
}

func TestBatchUpdateCredentialStatusIntegration(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}

fullCredentialID0, err := GetValue(credentialManifestContext, "credentialID0")
assert.NoError(t, err)
assert.NotEmpty(t, fullCredentialID0)

fullCredentialID1, err := GetValue(credentialManifestContext, "credentialID1")
assert.NoError(t, err)
assert.NotEmpty(t, fullCredentialID1)

credentialID0 := idFromURL(fullCredentialID0.(string))
credentialID1 := idFromURL(fullCredentialID1.(string))
updatesOutput, err := BatchUpdateVerifiableCredentialStatuses(batchUpdateStatusInputParams{
CredentialID0: credentialID0,
Suspended0: true,
CredentialID1: credentialID1,
Revoked1: true,
})
assert.NoError(t, err)
assert.NotEmpty(t, updatesOutput)

id0, err := getJSONElement(updatesOutput, "$.credentialStatuses[0].id")
assert.NoError(t, err)
assert.Equal(t, credentialID0, id0)

sus, err := getJSONElement(updatesOutput, "$.credentialStatuses[0].suspended")
assert.NoError(t, err)
assert.Equal(t, "true", sus)

id1, err := getJSONElement(updatesOutput, "$.credentialStatuses[1].id")
assert.NoError(t, err)
assert.Equal(t, credentialID1, id1)

rev, err := getJSONElement(updatesOutput, "$.credentialStatuses[1].revoked")
assert.NoError(t, err)
assert.Equal(t, "true", rev)
}

func TestBatchCreate100CredentialsIntegration(t *testing.T) {
Expand Down
12 changes: 12 additions & 0 deletions integration/testdata/batch-update-credential-statuses-input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"requests": [
{
"id": "{{.CredentialID0}}",
"suspended": {{.Suspended0}}
},
{
"id": "{{.CredentialID1}}",
"revoked": {{.Revoked1}}
}
]
}
66 changes: 66 additions & 0 deletions pkg/server/router/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,72 @@ type UpdateCredentialStatusResponse struct {
Suspended bool `json:"suspended"`
}

type SingleUpdateCredentialStatusRequest struct {
// ID of the credential who's status should be updated.
ID string `json:"id" validate:"required"`
UpdateCredentialStatusRequest
}

type BatchUpdateCredentialStatusRequest struct {
// Required. The list of update credential requests. Cannot be more than the config value in `services.credentials.batch_update_status_max_items`.
Requests []SingleUpdateCredentialStatusRequest `json:"requests" maxItems:"100" validate:"required,dive"`
}

func (r BatchUpdateCredentialStatusRequest) toServiceRequest() credential.BatchUpdateCredentialStatusRequest {
var req credential.BatchUpdateCredentialStatusRequest
for _, routerReq := range r.Requests {
serviceReq := routerReq.toServiceRequest(routerReq.ID)
req.Requests = append(req.Requests, serviceReq)
}
return req
}

type BatchUpdateCredentialStatusResponse struct {
CredentialStatuses []credential.Status `json:"credentialStatuses"`
}

// BatchUpdateCredentialStatus godoc
//
// @Summary Batch Update a Verifiable Credential's status
// @Description Updates the status all a batch of Verifiable Credentials.
// @Tags Credentials
// @Accept json
// @Produce json
// @Param request body BatchUpdateCredentialStatusRequest true "request body"
// @Success 201 {object} BatchUpdateCredentialStatusResponse
// @Failure 400 {string} string "Bad request"
// @Failure 500 {string} string "Internal server error"
// @Router /v1/credentials/status/batch [put]
func (cr CredentialRouter) BatchUpdateCredentialStatus(c *gin.Context) {
var batchRequest BatchUpdateCredentialStatusRequest
invalidCreateCredentialRequest := "invalid batch update credential request"
if err := framework.Decode(c.Request, &batchRequest); err != nil {
errMsg := invalidCreateCredentialRequest
framework.LoggingRespondErrWithMsg(c, err, errMsg, http.StatusBadRequest)
return
}

batchUpdateMaxItems := cr.service.Config().BatchUpdateStatusMaxItems
if len(batchRequest.Requests) > batchUpdateMaxItems {
framework.LoggingRespondErrMsg(c, fmt.Sprintf("max number of requests is %d", batchUpdateMaxItems), http.StatusBadRequest)
return
}

req := batchRequest.toServiceRequest()
batchUpdateResponse, err := cr.service.BatchUpdateCredentialStatus(c, req)

if err != nil {
errMsg := "could not update credentials"
framework.LoggingRespondErrWithMsg(c, err, errMsg, http.StatusInternalServerError)
return
}

var resp BatchUpdateCredentialStatusResponse
resp.CredentialStatuses = append(resp.CredentialStatuses, batchUpdateResponse.CredentialStatuses...)

framework.Respond(c, resp, http.StatusOK)
}

// UpdateCredentialStatus godoc
//
// @Summary Update a Verifiable Credential's status
Expand Down
5 changes: 4 additions & 1 deletion pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ const (
VerificationPath = "/verification"
WebhookPrefix = "/webhooks"
DIDConfigurationsPrefix = "/did-configurations"

batchSuffix = "/batch"
)

// SSIServer exposes all dependencies needed to run a http server and all its services
Expand Down Expand Up @@ -209,7 +211,7 @@ func CredentialAPI(rg *gin.RouterGroup, service svcframework.Service, webhookSer
// Credentials
credentialAPI := rg.Group(CredentialsPrefix)
credentialAPI.PUT("", middleware.Webhook(webhookService, webhook.Credential, webhook.Create), credRouter.CreateCredential)
credentialAPI.PUT("/batch", middleware.Webhook(webhookService, webhook.Credential, webhook.BatchCreate), credRouter.BatchCreateCredentials)
credentialAPI.PUT(batchSuffix, middleware.Webhook(webhookService, webhook.Credential, webhook.BatchCreate), credRouter.BatchCreateCredentials)
credentialAPI.GET("", credRouter.ListCredentials)
credentialAPI.GET("/:id", credRouter.GetCredential)
credentialAPI.PUT(VerificationPath, credRouter.VerifyCredential)
Expand All @@ -218,6 +220,7 @@ func CredentialAPI(rg *gin.RouterGroup, service svcframework.Service, webhookSer
// Credential Status
credentialAPI.GET("/:id"+StatusPrefix, credRouter.GetCredentialStatus)
credentialAPI.PUT("/:id"+StatusPrefix, credRouter.UpdateCredentialStatus)
credentialAPI.PUT(StatusPrefix+batchSuffix, credRouter.BatchUpdateCredentialStatus)
credentialAPI.GET(StatusPrefix+"/:id", credRouter.GetCredentialStatusList)
return
}
Expand Down
Loading

0 comments on commit f1ac3f8

Please sign in to comment.