Skip to content

Commit

Permalink
feat(sdk): Make credentials optional for GetSubmissionRequirements (#577
Browse files Browse the repository at this point in the history
)

GetSubmissionRequirements can now be called with no credentials passed in. This supports the use case where the caller has no credentials that match but wants to check the type constraint and/or schemas specified by the input descriptors.

Also removed an unused CredentialReader option and some unused error codes.

Signed-off-by: Derek Trider <[email protected]>
  • Loading branch information
Derek Trider authored Aug 30, 2023
1 parent 45ed540 commit 32dd9dd
Show file tree
Hide file tree
Showing 8 changed files with 28 additions and 124 deletions.
3 changes: 1 addition & 2 deletions cmd/wallet-sdk-gomobile/credential/inquirer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ package credential

import (
"encoding/json"
"errors"
"fmt"
"net/http"

Expand Down Expand Up @@ -76,7 +75,7 @@ func NewInquirer(opts *InquirerOpts) (*Inquirer, error) {
func (c *Inquirer) GetSubmissionRequirements(query []byte, credentials *verifiable.CredentialsArray,
) (*SubmissionRequirementArray, error) {
if credentials == nil {
return nil, errors.New("credentials must be provided")
credentials = verifiable.NewCredentialsArray()
}

pdQuery, err := unwrapQuery(query)
Expand Down
35 changes: 24 additions & 11 deletions cmd/wallet-sdk-gomobile/credential/inquirer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,30 @@ func TestInstance_GetSubmissionRequirements(t *testing.T) {
require.Equal(t, "VerifiableCredential", desc1.ID)
require.Equal(t, "VerifiableCredential", desc1.Name)
require.Equal(t, "So we can see that you are an expert.", desc1.Purpose)
require.Equal(t, desc1.MatchedVCs.Length(), 4)
require.Equal(t, 4, desc1.MatchedVCs.Length())
require.Equal(t, "", desc1.TypeConstraint())
require.Equal(t, 1, desc1.Schemas().Length())
schema := desc1.Schemas().AtIndex(0)
require.Equal(t, "VerifiableCredential", schema.URI())
require.False(t, schema.Required())
})

t.Run("Success with a nil credentials object", func(t *testing.T) {
query, err := credential.NewInquirer(opts)
require.NoError(t, err)

requirements, err := query.GetSubmissionRequirements(schemaPD, nil)

require.NoError(t, err)
require.Equal(t, requirements.Len(), 1)
req1 := requirements.AtIndex(0)

desc1 := req1.DescriptorAtIndex(0)

require.Equal(t, "VerifiableCredential", desc1.ID)
require.Equal(t, "VerifiableCredential", desc1.Name)
require.Equal(t, "So we can see that you are an expert.", desc1.Purpose)
require.Equal(t, 0, desc1.MatchedVCs.Length())
require.Equal(t, "", desc1.TypeConstraint())
require.Equal(t, 1, desc1.Schemas().Length())
schema := desc1.Schemas().AtIndex(0)
Expand Down Expand Up @@ -192,16 +215,6 @@ func TestInstance_GetSubmissionRequirements(t *testing.T) {

require.Contains(t, err.Error(), "validation of presentation definition failed:")
})

t.Run("Nil credentials", func(t *testing.T) {
query, err := credential.NewInquirer(opts)
require.NoError(t, err)

submissionRequirements, err := query.GetSubmissionRequirements(nil, nil)

require.EqualError(t, err, "credentials must be provided")
require.Nil(t, submissionRequirements)
})
}

func TestInstance_GetSubmissionRequirementsCitizenship(t *testing.T) {
Expand Down
1 change: 0 additions & 1 deletion cmd/wallet-sdk-gomobile/docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -1487,7 +1487,6 @@ interaction.presentCredentialUnsafe(preferredVC)
| INVALID_AUTHORIZATION_REQUEST(OVP1-0000) | The authorization request is a URI but specifies a scheme other than "openid-vc".<br/><br/>The authorization request is a URI and is missing the request_uri parameter.<br/><br/>The request object's signature is invalid.<br/><br/>The request object is malformed.<br/><br/>Wallet-SDK does not support the format/type of the authorization request and/or request object. |
| REQUEST_OBJECT_FETCH_FAILED(OVP1-0001) | The authorization request is a URI and the request URI endpoint that it specifies cannot be reached. |
| FAIL_TO_GET_MATCH_REQUIREMENTS_RESULTS(CRQ0-0004) | Invalid presentation definition received from the verifier. |
| NO_CREDENTIAL_SATISFY_REQUIREMENTS(CRQ0-0003) | None of your supplied credentials satisfy the requirements set by the verifier. Make sure you've gone through the full credential matching process correctly. See the OpenID4VP examples above. |
| CREATE_AUTHORIZED_RESPONSE(OVP1-0002) | No credentials provided in the `presentCredential` method call. |
| SEND_AUTHORIZED_RESPONSE(OVP1-0003) | The verifier server rejected your credentials (couldn't be verified, wrong type, etc).<br/><br/>The verifier server is down or incorrectly configured. |

Expand Down
10 changes: 0 additions & 10 deletions demo/app/lib/scenarios/handle_openid_vp_flow.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,6 @@ void handleOpenIDVpFlow(BuildContext context, String qrCodeURL) async {
log("stored credentials -> $storedCredentials");

credentials = storedCredentials.map((e) => e.value.rawCredential).toList();
if (credentials.isEmpty) {
log("credentials is empty now $credentials");
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CustomError(
requestErrorTitleMsg: "No Credentials found",
requestErrorSubTitleMsg: "Error found in the presentation flow")));
return;
}

try {
await walletSDKPlugin.processAuthorizationRequest(
Expand Down
8 changes: 0 additions & 8 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,6 @@ type DIDResolver interface {
Resolve(did string) (*did.DocResolution, error)
}

// A CredentialReader is capable of reading VCs from some underlying storage mechanism.
type CredentialReader interface {
// Get retrieves a VC.
Get(id string) (*verifiable.Credential, error)
// GetAll retrieves all VCs.
GetAll() ([]*verifiable.Credential, error)
}

// A CredentialWriter is capable of writing VCs to some underlying storage mechanism.
type CredentialWriter interface {
// Add adds a VC.
Expand Down
49 changes: 2 additions & 47 deletions pkg/credentialquery/credentialquery.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ SPDX-License-Identifier: Apache-2.0
package credentialquery

import (
"fmt"

"github.com/piprate/json-gold/ld"
"github.com/trustbloc/vc-go/presexch"
"github.com/trustbloc/vc-go/verifiable"
Expand All @@ -25,10 +23,7 @@ type Instance struct {
}

type queryOpts struct {
// credentials is an array of Verifiable Credentials.
credentials []*verifiable.Credential
// CredentialReader allows for access to a VC storage mechanism.
credentialReader api.CredentialReader

didResolver api.DIDResolver
applySelectiveDisclosure bool
Expand All @@ -37,21 +32,13 @@ type queryOpts struct {
// QueryOpt is the query credential option.
type QueryOpt func(opts *queryOpts)

// WithCredentialsArray sets array of Verifiable Credentials. If specified,
// this takes precedence over the CredentialReader option.
// WithCredentialsArray sets the array of Verifiable Credentials to check against the Presentation Definition.
func WithCredentialsArray(vcs []*verifiable.Credential) QueryOpt {
return func(opts *queryOpts) {
opts.credentials = vcs
}
}

// WithCredentialReader sets credential reader that will be used to fetch credential.
func WithCredentialReader(credentialReader api.CredentialReader) QueryOpt {
return func(opts *queryOpts) {
opts.credentialReader = credentialReader
}
}

// WithSelectiveDisclosure enables selective disclosure apply.
func WithSelectiveDisclosure(didResolver api.DIDResolver) QueryOpt {
return func(opts *queryOpts) {
Expand All @@ -75,11 +62,6 @@ func (c *Instance) GetSubmissionRequirements(
opt(qOpts)
}

credentials, err := getCredentials(qOpts)
if err != nil {
return nil, err
}

var matchOpts []presexch.MatchRequirementsOpt
if qOpts.applySelectiveDisclosure {
matchOpts = append(matchOpts,
Expand All @@ -93,7 +75,7 @@ func (c *Instance) GetSubmissionRequirements(
}

results, err := query.MatchSubmissionRequirement(
credentials,
qOpts.credentials,
c.documentLoader,
matchOpts...,
)
Expand All @@ -108,30 +90,3 @@ func (c *Instance) GetSubmissionRequirements(

return results, nil
}

func getCredentials(qOpts *queryOpts) ([]*verifiable.Credential, error) {
credentials := qOpts.credentials
if len(credentials) == 0 {
if qOpts.credentialReader == nil {
return nil, walleterror.NewValidationError(
module,
CredentialReaderNotSetCode,
CredentialReaderNotSetError,
fmt.Errorf("credentials array or credential reader option must be set"))
}

var err error

credentials, err = qOpts.credentialReader.GetAll()
if err != nil {
return nil,
walleterror.NewValidationError(
module,
CredentialReaderReadFailedCode,
CredentialReaderReadFailedError,
fmt.Errorf("credential reader failed: %w", err))
}
}

return credentials, nil
}
32 changes: 0 additions & 32 deletions pkg/credentialquery/credentialquery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package credentialquery_test
import (
_ "embed"
"encoding/json"
"errors"
"testing"

"github.com/google/uuid"
Expand Down Expand Up @@ -86,24 +85,6 @@ func TestInstance_GetSubmissionRequirements(t *testing.T) {
require.Len(t, requirements[0].Descriptors, 3)
})

t.Run("Reader error", func(t *testing.T) {
instance := credentialquery.NewInstance(docLoader)
_, err := instance.GetSubmissionRequirements(pdQuery, credentialquery.WithCredentialReader(
&readerMock{
err: errors.New("get all error"),
},
))

require.Error(t, err, "credential reader failed: get all error")
})

t.Run("Credentials not provided", func(t *testing.T) {
instance := credentialquery.NewInstance(docLoader)
_, err := instance.GetSubmissionRequirements(pdQuery)

testutil.RequireErrorContains(t, err, "CREDENTIAL_READER_NOT_SET")
})

t.Run("Checks schema", func(t *testing.T) {
incorrectPD := &presexch.PresentationDefinition{ID: uuid.New().String()}

Expand All @@ -117,19 +98,6 @@ func TestInstance_GetSubmissionRequirements(t *testing.T) {
})
}

type readerMock struct {
credentials []*verifiable.Credential
err error
}

func (r *readerMock) Get(string) (*verifiable.Credential, error) {
return nil, r.err
}

func (r *readerMock) GetAll() ([]*verifiable.Credential, error) {
return r.credentials, r.err
}

type didResolverMock struct {
ResolveValue *did.DocResolution
ResolveErr error
Expand Down
14 changes: 1 addition & 13 deletions pkg/credentialquery/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,7 @@ package credentialquery
// nolint:golint,nolintlint
const (
module = "CRQ"
CredentialReaderNotSetError = "CREDENTIAL_READER_NOT_SET" //nolint:gosec //false positive
CredentialReaderReadFailedError = "CREDENTIAL_READER_READ_FAILED" //nolint:gosec //false positive
CreateVPFailedError = "CREATE_VP_FAILED"
NoCredentialSatisfyRequirementsError = "NO_CREDENTIAL_SATISFY_REQUIREMENTS" //nolint:gosec //false positive
FailToGetMatchRequirementsResultsError = "FAIL_TO_GET_MATCH_REQUIREMENTS_RESULTS"
)

// Constants' names and reasons are obvious so they do not require additional comments.
// nolint:golint,nolintlint
const (
CredentialReaderNotSetCode = iota
CredentialReaderReadFailedCode
CreateVPFailedCode
NoCredentialSatisfyRequirementsCode
FailToGetMatchRequirementsResultsCode
)
const FailToGetMatchRequirementsResultsCode = 4 //nolint // Purpose is obvious from the name.

0 comments on commit 32dd9dd

Please sign in to comment.