From 9ff8c67ad3127983079e2a5399b82f7c6b593e4f Mon Sep 17 00:00:00 2001 From: talwinder50 Date: Fri, 21 Jan 2022 12:57:29 -0500 Subject: [PATCH] feat: [WACI-Issuance] Support to read credential manifest closes #561 Signed-off-by: talwinder50 --- cmd/adapter-rest/startcmd/start.go | 7 +- pkg/profile/issuer/profile.go | 6 +- pkg/restapi/issuer/operation/models.go | 3 + pkg/restapi/issuer/operation/operations.go | 43 ++++++++++-- .../issuer/operation/operations_test.go | 67 ++++++++++++++++--- pkg/restapi/issuer/operation/support_test.go | 1 + 6 files changed, 110 insertions(+), 17 deletions(-) diff --git a/cmd/adapter-rest/startcmd/start.go b/cmd/adapter-rest/startcmd/start.go index 4e92b02c..84d7f2d5 100644 --- a/cmd/adapter-rest/startcmd/start.go +++ b/cmd/adapter-rest/startcmd/start.go @@ -868,8 +868,8 @@ func addIssuerHandlers(parameters *adapterRestParameters, framework *aries.Aries if err != nil { return fmt.Errorf("aries-framework - failed to get aries context : %w", err) } - // TODO #572 Pass the output descriptors to issuer - _, err = readCMOutputDescriptorFile(parameters.cmOutputDescriptorsFilePath) + + cmOutputDescriptor, err := readCMOutputDescriptorFile(parameters.cmOutputDescriptorsFilePath) if err != nil { return fmt.Errorf("failed to read and validate manifest output descriptors : %w", err) } @@ -892,7 +892,7 @@ func addIssuerHandlers(parameters *adapterRestParameters, framework *aries.Aries if err != nil { return fmt.Errorf("failed to init trustbloc did creator: %w", err) } - // TODO #572 Pass the output descriptors to issuer + // TODO #579 Persist the manifest output descriptor in local file based or in-memory cache. // add issuer endpoints issuerService, err := issuer.New(&issuerops.Config{ AriesCtx: ariesCtx, @@ -907,6 +907,7 @@ func addIssuerHandlers(parameters *adapterRestParameters, framework *aries.Aries ExternalURL: parameters.externalURL, DidDomain: parameters.trustblocDomain, JSONLDDocumentLoader: ariesCtx.JSONLDDocumentLoader(), + CmOutputDescriptor: cmOutputDescriptor, }) if err != nil { return fmt.Errorf("failed to init issuer ops: %w", err) diff --git a/pkg/profile/issuer/profile.go b/pkg/profile/issuer/profile.go index 7130d023..f522d223 100644 --- a/pkg/profile/issuer/profile.go +++ b/pkg/profile/issuer/profile.go @@ -12,6 +12,7 @@ import ( "fmt" "time" + "github.com/hyperledger/aries-framework-go/pkg/doc/cm" "github.com/hyperledger/aries-framework-go/spi/storage" "github.com/trustbloc/edge-adapter/pkg/internal/common/adapterutil" @@ -30,6 +31,8 @@ type Profile struct { } // ProfileData struct for profile. +// Issuer ID identifies who is the issuer of the credential manifests being issued. +// CMStyle represents an entity styles object as defined in credential manifest spec. type ProfileData struct { ID string `json:"id,omitempty"` Name string `json:"name"` @@ -45,7 +48,8 @@ type ProfileData struct { OIDCClientParams *OIDCClientParams `json:"oidcParams,omitempty"` CredentialScopes []string `json:"credScopes,omitempty"` LinkedWalletURL string `json:"linkedWallet,omitempty"` - // Todo #issue Add credential manifest issuer object + IssuerID string `json:"issuerID,omitempty"` + CMStyle cm.Styles `json:"styles,omitempty"` } // OIDCClientParams optional set of oidc client parameters that the issuer may set, for static client registration. diff --git a/pkg/restapi/issuer/operation/models.go b/pkg/restapi/issuer/operation/models.go index 5a9ba952..4481988c 100644 --- a/pkg/restapi/issuer/operation/models.go +++ b/pkg/restapi/issuer/operation/models.go @@ -10,6 +10,7 @@ import ( "encoding/json" "github.com/hyperledger/aries-framework-go/pkg/client/outofband" + "github.com/hyperledger/aries-framework-go/pkg/doc/cm" adaptervc "github.com/trustbloc/edge-adapter/pkg/vc" ) @@ -27,6 +28,8 @@ type ProfileDataRequest struct { OIDCClientParams *OIDCClientParams `json:"oidcParams,omitempty"` CredentialScopes []string `json:"scopes,omitempty"` LinkedWalletURL string `json:"linkedWallet,omitempty"` + IssuerID string `json:"issuerID,omitempty"` + CMStyle cm.Styles `json:"styles,omitempty"` } // OIDCClientParams optional parameters for setting the adapter's oidc client parameters statically. diff --git a/pkg/restapi/issuer/operation/operations.go b/pkg/restapi/issuer/operation/operations.go index cce5dbbf..2fda86c0 100644 --- a/pkg/restapi/issuer/operation/operations.go +++ b/pkg/restapi/issuer/operation/operations.go @@ -104,6 +104,7 @@ const ( // WACI interaction constants credentialManifestFormat = "dif/credential-manifest/manifest@v1.0" + credentialManifestVersion = "0.1.0" credentialFulfillmentFormat = "dif/credential-manifest/fulfillment@v1.0" offerCredentialAttachMediaType = "application/json" redirectStatusOK = "OK" @@ -141,6 +142,7 @@ type didExClient interface { } // Config defines configuration for issuer operations. +// TODO #580 Create helper function for cmOutputDescriptor type Config struct { AriesCtx aries.CtxProvider AriesMessenger service.Messenger @@ -155,6 +157,7 @@ type Config struct { ExternalURL string DidDomain string JSONLDDocumentLoader ld.DocumentLoader + CmOutputDescriptor map[string][]*cm.OutputDescriptor } // New returns issuer rest instance. @@ -342,6 +345,7 @@ type Operation struct { getOIDCClientFunc func(string, string) (oidcClient, error) didDomain string jsonldDocLoader ld.DocumentLoader + cmOutputDescriptor map[string][]*cm.OutputDescriptor } // GetRESTHandlers get all controller API handler available for this service. @@ -360,6 +364,7 @@ func (o *Operation) GetRESTHandlers() []restapi.Handler { }, o.walletBridge.GetRESTHandlers()...) } +// TODO #581 Validate and check Waci profile creation that each scope has output descriptors configured. func (o *Operation) createIssuerProfileHandler(rw http.ResponseWriter, req *http.Request) { data := &ProfileDataRequest{} @@ -1290,8 +1295,15 @@ func (o *Operation) handleProposeCredential(msg service.DIDCommAction) (issuecre } } - // get manifest - manifest := o.readCredentialManifest() + txn, err := o.getTxn(userInvMap.TxID) + if err != nil { + return nil, fmt.Errorf("failed to get trasaction data: %w", err) + } + + // read credential manifest + manifest := o.readCredentialManifest(profile, txn.CredScope) + + // TODO #581 validate read credential manifest object // get unsigned credential vc, err := o.createCredential(getUserDataURL(profile.URL), userInvMap.TxToken, oauthToken, @@ -1728,11 +1740,30 @@ func (o *Operation) hanlDIDExStateMsg(msg service.StateMsg) error { return nil } -// read credential manifest from profile URL endpoints -// TODO for now returning empty manifest, TO BE IMPLEMENTED [issue##561 & issue#563] -func (o *Operation) readCredentialManifest() *cm.CredentialManifest { +// Read credential manifest issuer detail from persisted profile data and scope from persisted transaction cred scope. +// cm.Issuer's ID will be used as issuer ID which identifies who the issuer of the credential(s) will be. +// Credential Manifest Styles represents an Entity Styles object as defined in credential manifest spec. +// TODO issue#561 Add credential manifest presentation definition +func (o *Operation) readCredentialManifest(profileData *issuer.ProfileData, + txnCredScope string) *cm.CredentialManifest { + if cmDescriptor, ok := o.cmOutputDescriptor[txnCredScope]; ok { + return &cm.CredentialManifest{ + ID: uuid.NewString(), + Version: credentialManifestVersion, + Issuer: cm.Issuer{ + ID: profileData.IssuerID, + Name: profileData.Name, + Styles: profileData.CMStyle, + }, + OutputDescriptors: cmDescriptor, + } + } + return &cm.CredentialManifest{ ID: uuid.NewString(), + Issuer: cm.Issuer{ + ID: profileData.IssuerID, + }, } } @@ -2068,5 +2099,7 @@ func mapProfileReqToData(data *ProfileDataRequest, didDoc *did.Doc) (*issuer.Pro OIDCClientParams: clientParams, CredentialScopes: data.CredentialScopes, LinkedWalletURL: data.LinkedWalletURL, + IssuerID: data.IssuerID, + CMStyle: data.CMStyle, }, nil } diff --git a/pkg/restapi/issuer/operation/operations_test.go b/pkg/restapi/issuer/operation/operations_test.go index 3b06f956..28373d56 100644 --- a/pkg/restapi/issuer/operation/operations_test.go +++ b/pkg/restapi/issuer/operation/operations_test.go @@ -61,6 +61,7 @@ const ( inviteeDID = "did:example:0d76fa4e1386" inviterDID = "did:example:e6025bfdbb8f" mockOIDCProvider = "mock.provider.local" + mockCredScope = "prc" ) func TestNew(t *testing.T) { @@ -513,6 +514,7 @@ func TestCreateProfile(t *testing.T) { require.Equal(t, vReq.Name, profileRes.Name) require.Equal(t, vReq.URL, profileRes.URL) require.Equal(t, vReq.SupportsAssuranceCredential, profileRes.SupportsAssuranceCredential) + require.Equal(t, vReq.IssuerID, profileRes.IssuerID) require.Equal(t, vReq.SupportsWACI, profileRes.SupportsWACI) }) @@ -3454,6 +3456,15 @@ func TestWACIIssuanceHandler(t *testing.T) { ConnIDByDIDs: connID, } + c.cmOutputDescriptor = map[string][]*cm.OutputDescriptor{ + mockCredScope: { + &cm.OutputDescriptor{ + ID: uuid.New().String(), + Schema: "https://www.w3.org/2018/credentials/examples/v1", + }, + }, + } + invitationID := uuid.New().String() issuerID := uuid.New().String() @@ -3463,11 +3474,25 @@ func TestWACIIssuanceHandler(t *testing.T) { err = c.profileStore.SaveProfile(profile) require.NoError(t, err) - err = c.storeUserInvitationMapping(&UserInvitationMapping{ + usrInvitationMapping := &UserInvitationMapping{ InvitationID: invitationID, IssuerID: issuerID, + TxID: uuid.New().String(), TxToken: uuid.New().String(), - }) + } + + err = c.storeUserInvitationMapping(usrInvitationMapping) + require.NoError(t, err) + + txDataSample := &txnData{ + IssuerID: profile.ID, + CredScope: mockCredScope, + } + + tdByte, err := json.Marshal(txDataSample) + require.NoError(t, err) + + err = c.txnStore.Put(usrInvitationMapping.TxID, tdByte) require.NoError(t, err) go c.didCommActionListener(actionCh) @@ -3502,23 +3527,48 @@ func TestWACIIssuanceHandler(t *testing.T) { invitationID := uuid.New().String() issuerID := uuid.New().String() - + c.cmOutputDescriptor = map[string][]*cm.OutputDescriptor{ + mockCredScope: { + &cm.OutputDescriptor{ + ID: uuid.New().String(), + Schema: "https://www.w3.org/2018/credentials/examples/v1", + }, + }, + } profile := createProfileData(issuerID) profile.SupportsWACI = true err = c.profileStore.SaveProfile(profile) require.NoError(t, err) - err = c.storeUserInvitationMapping(&UserInvitationMapping{ + usrInvitationMapping := &UserInvitationMapping{ InvitationID: invitationID, IssuerID: issuerID, + TxID: uuid.New().String(), TxToken: uuid.New().String(), - }) + } + + err = c.storeUserInvitationMapping(usrInvitationMapping) require.NoError(t, err) go c.didCommActionListener(actionCh) + testFailure(actionCh, service.NewDIDCommMsgMap(issuecredsvc.ProposeCredentialV2{ + Type: issuecredsvc.ProposeCredentialMsgTypeV2, + InvitationID: invitationID, + }), "failed to fetch txn data") + + // validate manifest data error + txDataSample := &txnData{ + IssuerID: profile.ID, + } // credential data error + txDataSample.CredScope = mockCredScope + tdCredByte, err := json.Marshal(txDataSample) + require.NoError(t, err) + + err = c.txnStore.Put(usrInvitationMapping.TxID, tdCredByte) + require.NoError(t, err) c.httpClient = &mockHTTPClient{ respValue: &http.Response{ StatusCode: http.StatusInternalServerError, @@ -3603,14 +3653,15 @@ func TestWACIIssuanceHandler(t *testing.T) { InvitationID: newInvitationID, }), "failed to get OIDC access token for WACI transaction") - // token store put error newInvitationID = uuid.New().String() issuerID = uuid.New().String() - err = c.storeUserInvitationMapping(&UserInvitationMapping{ + usrInvitationMapping = &UserInvitationMapping{ InvitationID: newInvitationID, IssuerID: issuerID, + TxID: usrInvitationMapping.TxID, TxToken: uuid.New().String(), - }) + } + err = c.storeUserInvitationMapping(usrInvitationMapping) require.NoError(t, err) profile = createProfileData(issuerID) diff --git a/pkg/restapi/issuer/operation/support_test.go b/pkg/restapi/issuer/operation/support_test.go index 2e2b31bb..068d849d 100644 --- a/pkg/restapi/issuer/operation/support_test.go +++ b/pkg/restapi/issuer/operation/support_test.go @@ -188,6 +188,7 @@ func createProfileData(profileID string) *issuer.ProfileData { URL: "http://issuer.example.com", PresentationSigningKey: "did:example:123xyz#key-1", SupportsWACI: false, + IssuerID: "did:example:123?linked-domains=3", } }