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

Added manual overrides for credential issuance upon application review. #250

Merged
merged 1 commit into from
Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions integration/steelthread_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package integration
import (
"testing"

"github.com/TBD54566975/ssi-sdk/credential/util"
"github.com/stretchr/testify/assert"
"github.com/tbd54566975/ssi-service/pkg/service/operation/storage"
)
Expand Down Expand Up @@ -177,6 +178,10 @@ func TestSubmitAndReviewApplicationIntegration(t *testing.T) {
vc, err := getJSONElement(reviewApplicationOutput, "$.verifiableCredentials[0]")
assert.NoError(t, err)
assert.NotEmpty(t, vc)
typedVC, err := util.CredentialsFromInterface(vc)
assert.NoError(t, err)
assert.Equal(t, "Mister", typedVC.CredentialSubject["givenName"])
assert.Equal(t, "Tee", typedVC.CredentialSubject["familyName"])

operationOutput, err := get(endpoint + version + "operations/" + opID)
assert.NoError(t, err)
Expand Down
12 changes: 11 additions & 1 deletion integration/testdata/review-application-input.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
{
"approved": {{.Approved}},
"reason": "{{.Reason}}"
"reason": "{{.Reason}}",
"credential_overrides": {
"kyc_credential": {
"data": {
"familyName": "Tee",
"givenName": "Mister"
},
"expiry": null,
"revocable": true
}
}
}
11 changes: 8 additions & 3 deletions pkg/server/router/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,13 +517,18 @@ func (mr ManifestRouter) DeleteResponse(ctx context.Context, w http.ResponseWrit
type ReviewApplicationRequest struct {
Approved bool `json:"approved"`
Reason string `json:"reason"`

// Overrides to apply to the credentials that will be created. Keys are the ID that corresponds to an
// OutputDescriptor.ID from the manifest.
CredentialOverrides map[string]model.CredentialOverride `json:"credential_overrides,omitempty"`
}

func (r ReviewApplicationRequest) toServiceRequest(id string) model.ReviewApplicationRequest {
return model.ReviewApplicationRequest{
ID: id,
Approved: r.Approved,
Reason: r.Reason,
ID: id,
Approved: r.Approved,
Reason: r.Reason,
CredentialOverrides: r.CredentialOverrides,
}
}

Expand Down
33 changes: 32 additions & 1 deletion pkg/server/server_manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ import (
"github.com/tbd54566975/ssi-service/pkg/service/schema"
)

func TestFoo(t *testing.T) {
data := []byte(`{"credential_overrides":{"some_key":{}}}`)
var p router.ReviewApplicationRequest
assert.NoError(t, json.Unmarshal(data, &p))
assert.Equal(t, map[string]manifestsvc.CredentialOverride{
"some_key": {},
}, p.CredentialOverrides)
}
func TestManifestAPI(t *testing.T) {
t.Run("Test Create Manifest", func(tt *testing.T) {
bolt := setupTestDB(tt)
Expand Down Expand Up @@ -581,7 +589,20 @@ func TestManifestAPI(t *testing.T) {
assert.Contains(tt, op.ID, "credentials/responses/")

// review application
reviewApplicationRequestValue := newRequestValue(tt, router.ReviewApplicationRequest{Approved: true, Reason: "I'm the almighty approver"})
expireAt := time.Date(2025, 10, 32, 0, 0, 0, 0, time.UTC)
reviewApplicationRequestValue := newRequestValue(tt, router.ReviewApplicationRequest{
Approved: true,
Reason: "I'm the almighty approver",
CredentialOverrides: map[string]manifestsvc.CredentialOverride{
"id1": {
Data: map[string]any{
"looks": "pretty darn handsome",
},
Expiry: &expireAt,
Revocable: true,
},
},
})
applicationID := storage.StatusObjectID(op.ID)
req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests/applications/"+applicationID+"/review", reviewApplicationRequestValue)
err = manifestRouter.ReviewApplication(newRequestContextWithParams(map[string]string{"id": applicationID}), w, req)
Expand All @@ -597,6 +618,16 @@ func TestManifestAPI(t *testing.T) {
assert.Len(tt, appResp.Response.Fulfillment.DescriptorMap, 2)
assert.Len(tt, appResp.Credentials, 2)
assert.Empty(tt, appResp.Response.Denial)

vc, err := util.CredentialsFromInterface(appResp.Credentials.([]any)[0])
assert.NoError(tt, err)
assert.Equal(tt, credsdk.CredentialSubject{
"id": applicantDID.DID.ID,
"looks": "pretty darn handsome",
}, vc.CredentialSubject)
assert.Equal(tt, expireAt.Format(time.RFC3339), vc.ExpirationDate)
assert.NotEmpty(tt, vc.CredentialStatus)
assert.Equal(tt, createdSchema.ID, vc.CredentialSchema.ID)
})

t.Run("Test Denied Application", func(tt *testing.T) {
Expand Down
15 changes: 15 additions & 0 deletions pkg/service/manifest/model/model.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package model

import (
"time"

"github.com/TBD54566975/ssi-sdk/credential/exchange"
manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest"
cred "github.com/tbd54566975/ssi-service/internal/credential"
Expand Down Expand Up @@ -93,6 +95,8 @@ type ReviewApplicationRequest struct {
ID string `json:"id" validate:"required"`
Approved bool `json:"approved"`
Reason string `json:"reason"`

CredentialOverrides map[string]CredentialOverride `json:"credential_overrides,omitempty"`
}

// Response
Expand Down Expand Up @@ -121,3 +125,14 @@ func ServiceModel(storedResponse *storage.StoredResponse) SubmitApplicationRespo
ResponseJWT: storedResponse.ResponseJWT,
}
}

type CredentialOverride struct {
// Data that will be used to determine credential claims.
Data map[string]any `json:"data"`

// Parameter to determine the expiry of the credential.
Expiry *time.Time `json:"expiry"`

// Whether the credentials created should be revocable.
Revocable bool `json:"revocable"`
}
16 changes: 16 additions & 0 deletions pkg/service/manifest/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/tbd54566975/ssi-service/pkg/service/credential"
"github.com/tbd54566975/ssi-service/pkg/service/issuing"
"github.com/tbd54566975/ssi-service/pkg/service/keystore"
"github.com/tbd54566975/ssi-service/pkg/service/manifest/model"
)

const (
Expand Down Expand Up @@ -56,6 +57,7 @@ func (s Service) buildCredentialResponse(
template *issuing.IssuanceTemplate,
application manifest.CredentialApplication,
applicationJSON map[string]any,
credentialOverrides map[string]model.CredentialOverride,
) (*manifest.CredentialResponse, []cred.Container, error) {
// TODO(gabe) need to check if this can be fulfilled and conditionally return success/denial
applicationID := application.ID
Expand Down Expand Up @@ -98,6 +100,7 @@ func (s Service) buildCredentialResponse(
return nil, nil, err
}
}
s.applyRequestData(&credentialRequest, credentialOverrides, od)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

methods without return values make me nervous!


credentialResponse, err := s.credential.CreateCredential(ctx, credentialRequest)
if err != nil {
Expand Down Expand Up @@ -146,6 +149,19 @@ func (s Service) buildCredentialResponse(
return credRes, creds, nil
}

func (s Service) applyRequestData(credentialRequest *credential.CreateCredentialRequest, credentialOverrides map[string]model.CredentialOverride, od manifest.OutputDescriptor) {
if credentialOverride, ok := credentialOverrides[od.ID]; ok {
for k, v := range credentialOverride.Data {
credentialRequest.Data[k] = v
}

if credentialOverride.Expiry != nil {
credentialRequest.Expiry = credentialOverride.Expiry.Format(time.RFC3339)
}
credentialRequest.Revocable = credentialOverride.Revocable
}
}

func (s Service) applyIssuanceTemplate(
credentialRequest *credential.CreateCredentialRequest,
template *issuing.IssuanceTemplate,
Expand Down
13 changes: 2 additions & 11 deletions pkg/service/manifest/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,17 +374,7 @@ func (s Service) maybeIssueAutomatically(
logrus.Warnf("found multiple issuance templates, using first entry only")
}

credResp, creds, err := s.buildCredentialResponse(
ctx,
applicantDID,
manifestID,
gotManifest.Manifest,
true,
"automatic creation via issuance template",
&issuanceTemplate,
request.Application,
request.ApplicationJSON,
)
credResp, creds, err := s.buildCredentialResponse(ctx, applicantDID, manifestID, gotManifest.Manifest, true, "automatic creation via issuance template", &issuanceTemplate, request.Application, request.ApplicationJSON, nil)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -458,6 +448,7 @@ func (s Service) ReviewApplication(ctx context.Context, request model.ReviewAppl
nil,
application.Application,
nil,
request.CredentialOverrides,
)
if err != nil {
return nil, util.LoggingErrorMsg(err, "could not build credential response")
Expand Down