Skip to content

Commit

Permalink
Merge pull request #1725 from skynet2/initiate-issuance-validation
Browse files Browse the repository at this point in the history
feat: perform strict validation
  • Loading branch information
fqutishat authored May 23, 2024
2 parents 9a36f72 + 18e61ea commit b872390
Show file tree
Hide file tree
Showing 10 changed files with 452 additions and 213 deletions.
383 changes: 192 additions & 191 deletions api/spec/openapi.gen.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions cmd/vc-rest/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,7 @@ func buildEchoHandler(
TrustRegistry: trustRegistryService,
AckService: ackService,
Composer: oidc4ci.NewCredentialComposer(),
DocumentLoader: documentLoader,
})
if err != nil {
return nil, fmt.Errorf("failed to instantiate new oidc4ci service: %w", err)
Expand Down
8 changes: 8 additions & 0 deletions docs/v1/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1517,6 +1517,10 @@ components:
type: boolean
description: Override credential subject did.
nullable: true
perform_strict_validation:
type: boolean
description: Perform strict validation.
nullable: true
credential:
type: object
description: Raw Complete credential for sign and customization
Expand Down Expand Up @@ -2250,6 +2254,10 @@ components:
type: boolean
description: Override credential subject did.
nullable: true
credential_perform_strict_validation:
type: boolean
description: Perform strict validation.
nullable: true
credential:
type: object
description: Raw Complete credential for sign and customization
Expand Down
18 changes: 10 additions & 8 deletions pkg/restapi/v1/issuer/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,10 +425,11 @@ func (c *Controller) InitiateCredentialComposeIssuance(e echo.Context, profileID
for _, compose := range lo.FromPtr(body.Compose) {
configs = append(configs, InitiateIssuanceCredentialConfiguration{
Compose: &DeprecatedComposeOIDC4CICredential{
Credential: compose.Credential,
IdTemplate: compose.CredentialOverrideId,
OverrideIssuer: compose.CredentialOverrideIssuer,
OverrideSubjectDid: compose.CredentialOverrideSubjectDid,
Credential: compose.Credential,
IdTemplate: compose.CredentialOverrideId,
OverrideIssuer: compose.CredentialOverrideIssuer,
OverrideSubjectDid: compose.CredentialOverrideSubjectDid,
PerformStrictValidation: compose.CredentialPerformStrictValidation,
},
CredentialExpiresAt: compose.CredentialExpiresAt,
})
Expand Down Expand Up @@ -524,10 +525,11 @@ func (c *Controller) initiateIssuance(

if multiCredentialIssuance.Compose != nil {
credConfig.ComposeCredential = &oidc4ci.InitiateIssuanceComposeCredential{
Credential: multiCredentialIssuance.Compose.Credential,
IDTemplate: lo.FromPtr(multiCredentialIssuance.Compose.IdTemplate),
OverrideIssuer: lo.FromPtr(multiCredentialIssuance.Compose.OverrideIssuer),
OverrideSubjectDID: lo.FromPtr(multiCredentialIssuance.Compose.OverrideSubjectDid),
Credential: multiCredentialIssuance.Compose.Credential,
IDTemplate: lo.FromPtr(multiCredentialIssuance.Compose.IdTemplate),
OverrideIssuer: lo.FromPtr(multiCredentialIssuance.Compose.OverrideIssuer),
OverrideSubjectDID: lo.FromPtr(multiCredentialIssuance.Compose.OverrideSubjectDid),
PerformStrictValidation: lo.FromPtr(multiCredentialIssuance.Compose.PerformStrictValidation),
}
}

Expand Down
73 changes: 73 additions & 0 deletions pkg/restapi/v1/issuer/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,79 @@ func TestController_initiateCredentialIssuance_CompatibilityV1(t *testing.T) {
})
}

func TestController_ComposeIssuance(t *testing.T) {
issuerProfile := &profileapi.Issuer{
OrganizationID: orgID,
ID: profileID,
Version: profileVersion,
Active: true,
OIDCConfig: &profileapi.OIDCConfig{},
CredentialTemplates: []*profileapi.CredentialTemplate{
{
ID: "templateID",
},
},
}

var (
mockProfileSvc = NewMockProfileService(gomock.NewController(t))
mockOIDC4CISvc = NewMockOIDC4CIService(gomock.NewController(t))
mockEventSvc = NewMockEventService(gomock.NewController(t))
c echo.Context
)

t.Run("Success", func(t *testing.T) {
expectedCred := map[string]interface{}{
"a": "b",
}
req, err := json.Marshal(&InitiateOIDC4CIComposeRequest{
ClientInitiateIssuanceUrl: lo.ToPtr("https://wallet.example.com/initiate_issuance"),
ClientWellknown: lo.ToPtr("https://wallet.example.com/.well-known/openid-configuration"),
Compose: lo.ToPtr([]InitiateIssuanceCredentialConfigurationCompose{
{
CredentialOverrideId: lo.ToPtr("abc"),
Credential: &expectedCred,
},
}),
})

require.NoError(t, err)

resp := &oidc4ci.InitiateIssuanceResponse{
InitiateIssuanceURL: "https://wallet.example.com/initiate_issuance",
TxID: "txID",
}

mockProfileSvc.EXPECT().GetProfile(profileID, profileVersion).Times(1).Return(issuerProfile, nil)
mockOIDC4CISvc.EXPECT().InitiateIssuance(gomock.Any(), gomock.Any(), issuerProfile).
DoAndReturn(func(
ctx context.Context,
request *oidc4ci.InitiateIssuanceRequest,
issuer *profileapi.Issuer,
) (*oidc4ci.InitiateIssuanceResponse, error) {
require.Len(t, request.CredentialConfiguration, 1)
require.EqualValues(t, expectedCred,
*request.CredentialConfiguration[0].ComposeCredential.Credential)

return resp, nil
})
mockEventSvc.EXPECT().Publish(gomock.Any(), spi.IssuerEventTopic, gomock.Any()).Times(0)

controller := NewController(&Config{
ProfileSvc: mockProfileSvc,
OIDC4CIService: mockOIDC4CISvc,
EventSvc: mockEventSvc,
EventTopic: spi.IssuerEventTopic,
Tracer: trace.NewNoopTracerProvider().Tracer(""),
})

c = echoContext(withRequestBody(req))

err = controller.InitiateCredentialComposeIssuance(c, profileID, profileVersion)
require.NoError(t, err)
})
}

func TestController_InitiateCredentialIssuance(t *testing.T) {
issuerProfile := &profileapi.Issuer{
OrganizationID: orgID,
Expand Down
6 changes: 6 additions & 0 deletions pkg/restapi/v1/issuer/openapi.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions pkg/service/oidc4ci/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,11 @@ type InitiateIssuanceCredentialConfiguration struct {
}

type InitiateIssuanceComposeCredential struct {
Credential *map[string]interface{} `json:"credential,omitempty"`
IDTemplate string `json:"id_template"`
OverrideIssuer bool `json:"override_issuer"`
OverrideSubjectDID bool `json:"override_subject_did"`
Credential *map[string]interface{} `json:"credential,omitempty"`
IDTemplate string `json:"id_template"`
OverrideIssuer bool `json:"override_issuer"`
OverrideSubjectDID bool `json:"override_subject_did"`
PerformStrictValidation bool `json:"perform_strict_validation,omitempty"`
}

// InitiateIssuanceResponse is the response from the Issuer to the Wallet with initiate issuance URL.
Expand Down
11 changes: 10 additions & 1 deletion pkg/service/oidc4ci/oidc4ci_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

//go:generate mockgen -destination oidc4ci_service_mocks_test.go -self_package mocks -package oidc4ci_test -source=oidc4ci_service.go -mock_names transactionStore=MockTransactionStore,wellKnownService=MockWellKnownService,eventService=MockEventService,pinGenerator=MockPinGenerator,credentialOfferReferenceStore=MockCredentialOfferReferenceStore,claimDataStore=MockClaimDataStore,profileService=MockProfileService,dataProtector=MockDataProtector,kmsRegistry=MockKMSRegistry,cryptoJWTSigner=MockCryptoJWTSigner,jsonSchemaValidator=MockJSONSchemaValidator,trustRegistry=MockTrustRegistry,ackStore=MockAckStore,ackService=MockAckService,composer=MockComposer
//go:generate mockgen -destination oidc4ci_service_mocks_test.go -self_package mocks -package oidc4ci_test -source=oidc4ci_service.go -mock_names transactionStore=MockTransactionStore,wellKnownService=MockWellKnownService,eventService=MockEventService,pinGenerator=MockPinGenerator,credentialOfferReferenceStore=MockCredentialOfferReferenceStore,claimDataStore=MockClaimDataStore,profileService=MockProfileService,dataProtector=MockDataProtector,kmsRegistry=MockKMSRegistry,cryptoJWTSigner=MockCryptoJWTSigner,jsonSchemaValidator=MockJSONSchemaValidator,trustRegistry=MockTrustRegistry,ackStore=MockAckStore,ackService=MockAckService,composer=MockComposer,documentLoader=MockDocumentLoader

package oidc4ci

Expand All @@ -19,6 +19,7 @@ import (
"time"

"github.com/google/uuid"
"github.com/piprate/json-gold/ld"
"github.com/samber/lo"
util "github.com/trustbloc/did-go/doc/util/time"
"github.com/trustbloc/logutil-go/pkg/log"
Expand Down Expand Up @@ -153,6 +154,11 @@ type composer interface {
) (*verifiable.Credential, error)
}

// DocumentLoader knows how to load remote documents.
type documentLoader interface {
LoadDocument(u string) (*ld.RemoteDocument, error)
}

// Config holds configuration options and dependencies for Service.
type Config struct {
TransactionStore transactionStore
Expand All @@ -173,6 +179,7 @@ type Config struct {
TrustRegistry trustRegistry
AckService ackService
Composer composer
DocumentLoader documentLoader
}

// Service implements VCS credential interaction API for OIDC credential issuance.
Expand All @@ -195,6 +202,7 @@ type Service struct {
trustRegistry trustRegistry
ackService ackService
composer composer
documentLoader documentLoader
}

// NewService returns a new Service instance.
Expand All @@ -218,6 +226,7 @@ func NewService(config *Config) (*Service, error) {
trustRegistry: config.TrustRegistry,
ackService: config.AckService,
composer: config.Composer,
documentLoader: config.DocumentLoader,
}, nil
}

Expand Down
48 changes: 45 additions & 3 deletions pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/samber/lo"
"github.com/trustbloc/logutil-go/pkg/log"
"github.com/trustbloc/vc-go/jwt"
verifiable2 "github.com/trustbloc/vc-go/verifiable"

"github.com/trustbloc/vcs/internal/logfields"
"github.com/trustbloc/vcs/pkg/doc/vc"
Expand Down Expand Up @@ -183,10 +184,17 @@ func (s *Service) newTxCredentialConf(

var targetCredentialTemplate *profileapi.CredentialTemplate

if credentialConfiguration.CredentialTemplateID == "" &&
credentialConfiguration.ComposeCredential != nil &&
credentialConfiguration.ComposeCredential.Credential != nil {
isCompose := credentialConfiguration.ComposeCredential != nil &&
credentialConfiguration.ComposeCredential.Credential != nil

if credentialConfiguration.CredentialTemplateID == "" && isCompose { //nolint:nestif
targetCredentialTemplate = s.buildVirtualTemplate(&credentialConfiguration)

if targetCredentialTemplate.Checks.Strict { //nolint:nestif
if err = s.validateComposeCredential(*credentialConfiguration.ComposeCredential.Credential); err != nil {
return nil, err
}
}
} else {
targetCredentialTemplate, err = findCredentialTemplate(credentialConfiguration.CredentialTemplateID, profile)
if err != nil {
Expand Down Expand Up @@ -235,9 +243,43 @@ func (s *Service) newTxCredentialConf(
return txCredentialConfiguration, nil
}

func (s *Service) validateComposeCredential(credential map[string]interface{}) error {
requiredFields := map[string]string{
"issuer": "did:orb:anything",
"issuanceDate": "2021-01-01T00:00:00Z",
}

var missingFieldsAdded []string

for key, value := range requiredFields {
if _, ok := credential[key]; !ok {
credential[key] = value
missingFieldsAdded = append(missingFieldsAdded, key)
}
}

if _, credCheckErr := verifiable2.ParseCredentialJSON(credential,
verifiable2.WithJSONLDDocumentLoader(s.documentLoader),
verifiable2.WithDisabledProofCheck(),
verifiable2.WithStrictValidation(),
); credCheckErr != nil {
return resterr.NewValidationError(resterr.InvalidValue, "credential",
fmt.Errorf("parse credential: %w", credCheckErr))
}

for _, key := range missingFieldsAdded {
delete(credential, key)
}

return nil
}

func (s *Service) buildVirtualTemplate(req *InitiateIssuanceCredentialConfiguration) *profileapi.CredentialTemplate {
result := &profileapi.CredentialTemplate{
ID: fmt.Sprintf("virtual_%s", uuid.NewString()),
Checks: profileapi.CredentialTemplateChecks{
Strict: req.ComposeCredential.PerformStrictValidation,
},
}

if req.ComposeCredential.Credential != nil {
Expand Down
Loading

0 comments on commit b872390

Please sign in to comment.