Skip to content
This repository has been archived by the owner on Apr 5, 2023. It is now read-only.

feat: [WACI-Issuance] Support to read credential manifest #574

Merged
merged 1 commit into from
Jan 26, 2022
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
7 changes: 4 additions & 3 deletions cmd/adapter-rest/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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,
Expand All @@ -907,6 +907,7 @@ func addIssuerHandlers(parameters *adapterRestParameters, framework *aries.Aries
ExternalURL: parameters.externalURL,
DidDomain: parameters.trustblocDomain,
JSONLDDocumentLoader: ariesCtx.JSONLDDocumentLoader(),
CmOutputDescriptor: cmOutputDescriptor,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should be CMOutputDescriptor for consistency with the naming conventions

})
if err != nil {
return fmt.Errorf("failed to init issuer ops: %w", err)
Expand Down
6 changes: 5 additions & 1 deletion pkg/profile/issuer/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"`
Expand All @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions pkg/restapi/issuer/operation/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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"`
Copy link
Member

@sudeshrshetty sudeshrshetty Jan 26, 2022

Choose a reason for hiding this comment

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

follow up: please add comments explaining usage of these params so that it can show up in documentation.
(can be done in future PRs)

CMStyle cm.Styles `json:"styles,omitempty"`
}

// OIDCClientParams optional parameters for setting the adapter's oidc client parameters statically.
Expand Down
43 changes: 38 additions & 5 deletions pkg/restapi/issuer/operation/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -155,6 +157,7 @@ type Config struct {
ExternalURL string
DidDomain string
JSONLDDocumentLoader ld.DocumentLoader
CmOutputDescriptor map[string][]*cm.OutputDescriptor
Copy link
Member

Choose a reason for hiding this comment

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

this object will be heavy, I would suggest you put a helper struct here which has functions to find and return subset of output descriptors by credential scope. So that you can swap underlying memory store with cache in future with little effort.

Copy link
Member Author

Choose a reason for hiding this comment

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

#580 I will address this optimization in the follow up pr , if its okay with you @sudeshrshetty

}

// New returns issuer rest instance.
Expand Down Expand Up @@ -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.
Expand All @@ -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{}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Copy link
Member

@sudeshrshetty sudeshrshetty Jan 26, 2022

Choose a reason for hiding this comment

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

I don't see anywhere these being set to profile data??
Where is profile creation step which accepts these 2 additional params - IssuerID & CMStyle?

Copy link
Member Author

Choose a reason for hiding this comment

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

IssuerID: data.IssuerID,
Its being set here

Copy link
Member

Choose a reason for hiding this comment

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

@talwinder50 I meant how an issuer can pass it? I don't see these as part of profile creation request

Copy link
Member Author

Choose a reason for hiding this comment

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

@sudeshrshetty It is mentioned here

IssuerID string `json:"issuerID,omitempty"`

Name: profileData.Name,
Styles: profileData.CMStyle,
},
OutputDescriptors: cmDescriptor,
}
}

return &cm.CredentialManifest{
ID: uuid.NewString(),
Issuer: cm.Issuer{
ID: profileData.IssuerID,
},
}
}

Expand Down Expand Up @@ -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
}
67 changes: 59 additions & 8 deletions pkg/restapi/issuer/operation/operations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const (
inviteeDID = "did:example:0d76fa4e1386"
inviterDID = "did:example:e6025bfdbb8f"
mockOIDCProvider = "mock.provider.local"
mockCredScope = "prc"
)

func TestNew(t *testing.T) {
Expand Down Expand Up @@ -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)
})

Expand Down Expand Up @@ -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()

Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions pkg/restapi/issuer/operation/support_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
}

Expand Down