github.com/hyperledger/aries-framework-go@v0.3.2/pkg/client/issuecredential/client.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package issuecredential 8 9 import ( 10 "errors" 11 12 "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" 13 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator" 14 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/issuecredential" 15 issuecredentialmiddleware "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/middleware/issuecredential" 16 "github.com/hyperledger/aries-framework-go/pkg/store/connection" 17 ) 18 19 const ( 20 // web redirect decorator. 21 webRedirectDecorator = "~web-redirect" 22 webRedirectStatusFAIL = "FAIL" 23 ) 24 25 var ( 26 errEmptyOffer = errors.New("received an empty offer") 27 errEmptyProposal = errors.New("received an empty proposal") 28 errEmptyRequest = errors.New("received an empty request") 29 ) 30 31 type ( 32 // OfferCredential is a message sent by the Issuer to the potential Holder, 33 // describing the credential they intend to offer and possibly the price they expect to be paid. 34 OfferCredential = issuecredential.OfferCredentialParams 35 // OfferCredentialV2 is a message sent by the Issuer to the potential Holder, 36 // describing the credential they intend to offer and possibly the price they expect to be paid. 37 OfferCredentialV2 issuecredential.OfferCredentialV2 38 // OfferCredentialV3 is a message sent by the Issuer to the potential Holder, 39 // describing the credential they intend to offer and possibly the price they expect to be paid. 40 OfferCredentialV3 issuecredential.OfferCredentialV3 41 // ProposeCredential is an optional message sent by the potential Holder to the Issuer 42 // to initiate the protocol or in response to a offer-credential message when the Holder 43 // wants some adjustments made to the credential data offered by Issuer. 44 ProposeCredential = issuecredential.ProposeCredentialParams 45 // ProposeCredentialV2 is an optional message sent by the potential Holder to the Issuer 46 // to initiate the protocol or in response to a offer-credential message when the Holder 47 // wants some adjustments made to the credential data offered by Issuer. 48 ProposeCredentialV2 issuecredential.ProposeCredentialV2 49 // ProposeCredentialV3 is an optional message sent by the potential Holder to the Issuer 50 // to initiate the protocol or in response to a offer-credential message when the Holder 51 // wants some adjustments made to the credential data offered by Issuer. 52 ProposeCredentialV3 issuecredential.ProposeCredentialV3 53 // RequestCredential is a message sent by the potential Holder to the Issuer, 54 // to request the issuance of a credential. Where circumstances do not require 55 // a preceding Offer Credential message (e.g., there is no cost to issuance 56 // that the Issuer needs to explain in advance, and there is no need for cryptographic negotiation), 57 // this message initiates the protocol. 58 RequestCredential = issuecredential.RequestCredentialParams 59 // RequestCredentialV2 is a message sent by the potential Holder to the Issuer, 60 // to request the issuance of a credential. Where circumstances do not require 61 // a preceding Offer Credential message (e.g., there is no cost to issuance 62 // that the Issuer needs to explain in advance, and there is no need for cryptographic negotiation), 63 // this message initiates the protocol. 64 RequestCredentialV2 issuecredential.RequestCredentialV2 65 // RequestCredentialV3 is a message sent by the potential Holder to the Issuer, 66 // to request the issuance of a credential. Where circumstances do not require 67 // a preceding Offer Credential message (e.g., there is no cost to issuance 68 // that the Issuer needs to explain in advance, and there is no need for cryptographic negotiation), 69 // this message initiates the protocol. 70 RequestCredentialV3 issuecredential.RequestCredentialV3 71 // IssueCredential contains as attached payload the credentials being issued and is 72 // sent in response to a valid Invitation Credential message. 73 IssueCredential = issuecredential.IssueCredentialParams 74 // IssueCredentialV2 contains as attached payload the credentials being issued and is 75 // sent in response to a valid Invitation Credential message. 76 IssueCredentialV2 issuecredential.IssueCredentialV2 //nolint: golint 77 // IssueCredentialV3 contains as attached payload the credentials being issued and is 78 // sent in response to a valid Invitation Credential message. 79 IssueCredentialV3 issuecredential.IssueCredentialV3 //nolint: golint 80 // Action contains helpful information about action. 81 Action issuecredential.Action 82 ) 83 84 // Provider contains dependencies for the issuecredential protocol and is typically created by using aries.Context(). 85 type Provider interface { 86 Service(id string) (interface{}, error) 87 } 88 89 // ProtocolService defines the issuecredential service. 90 type ProtocolService interface { 91 service.DIDComm 92 Actions() ([]issuecredential.Action, error) 93 ActionContinue(piID string, opt ...issuecredential.Opt) error 94 ActionStop(piID string, err error, opt ...issuecredential.Opt) error 95 } 96 97 // Client enable access to issuecredential API. 98 type Client struct { 99 service.Event 100 service ProtocolService 101 } 102 103 // New return new instance of the issuecredential client. 104 func New(ctx Provider) (*Client, error) { 105 raw, err := ctx.Service(issuecredential.Name) 106 if err != nil { 107 return nil, err 108 } 109 110 svc, ok := raw.(ProtocolService) 111 if !ok { 112 return nil, errors.New("cast service to issuecredential service failed") 113 } 114 115 return &Client{ 116 Event: svc, 117 service: svc, 118 }, nil 119 } 120 121 // Actions returns unfinished actions for the async usage. 122 func (c *Client) Actions() ([]Action, error) { 123 actions, err := c.service.Actions() 124 if err != nil { 125 return nil, err 126 } 127 128 result := make([]Action, len(actions)) 129 for i, action := range actions { 130 result[i] = Action(action) 131 } 132 133 return result, nil 134 } 135 136 // SendOffer is used by the Issuer to send an offer. 137 func (c *Client) SendOffer(offer *OfferCredential, conn *connection.Record) (string, error) { 138 if offer == nil { 139 return "", errEmptyOffer 140 } 141 142 var msg service.DIDCommMsg 143 144 switch conn.DIDCommVersion { 145 default: 146 fallthrough 147 case service.V1: 148 offer.Type = issuecredential.OfferCredentialMsgTypeV2 149 150 msg = service.NewDIDCommMsgMap(offer.AsV2()) 151 case service.V2: 152 offer.Type = issuecredential.OfferCredentialMsgTypeV3 153 154 msg = service.NewDIDCommMsgMap(offer.AsV3()) 155 } 156 157 return c.service.HandleOutbound(msg, conn.MyDID, conn.TheirDID) 158 } 159 160 // SendProposal is used by the Holder to send a proposal. 161 func (c *Client) SendProposal(proposal *ProposeCredential, conn *connection.Record) (string, error) { 162 if proposal == nil { 163 return "", errEmptyProposal 164 } 165 166 var msg service.DIDCommMsg 167 168 switch conn.DIDCommVersion { 169 default: 170 fallthrough 171 case service.V1: 172 proposal.Type = issuecredential.ProposeCredentialMsgTypeV2 173 174 msg = service.NewDIDCommMsgMap(proposal.AsV2()) 175 case service.V2: 176 proposal.Type = issuecredential.ProposeCredentialMsgTypeV3 177 178 msg = service.NewDIDCommMsgMap(proposal.AsV3()) 179 } 180 181 return c.service.HandleOutbound(msg, conn.MyDID, conn.TheirDID) 182 } 183 184 // SendRequest is used by the Holder to send a request. 185 func (c *Client) SendRequest(request *RequestCredential, conn *connection.Record) (string, error) { 186 if request == nil { 187 return "", errEmptyRequest 188 } 189 190 var msg service.DIDCommMsg 191 192 switch conn.DIDCommVersion { 193 default: 194 fallthrough 195 case service.V1: 196 request.Type = issuecredential.RequestCredentialMsgTypeV2 197 198 msg = service.NewDIDCommMsgMap(request.AsV2()) 199 case service.V2: 200 request.Type = issuecredential.RequestCredentialMsgTypeV3 201 202 msg = service.NewDIDCommMsgMap(request.AsV3()) 203 } 204 205 return c.service.HandleOutbound(msg, conn.MyDID, conn.TheirDID) 206 } 207 208 // AcceptProposal is used when the Issuer is willing to accept the proposal. 209 // NOTE: For async usage. 210 func (c *Client) AcceptProposal(piID string, msg *OfferCredential) error { 211 return c.service.ActionContinue(piID, WithOfferCredential(msg)) 212 } 213 214 // AcceptOffer is used when the Holder is willing to accept the offer. 215 func (c *Client) AcceptOffer(piID string, msg *RequestCredential) error { 216 return c.service.ActionContinue(piID, WithRequestCredential(msg)) 217 } 218 219 // NegotiateProposal is used when the Holder wants to negotiate about an offer he received. 220 // NOTE: For async usage. This function can be used only after receiving OfferCredential. 221 func (c *Client) NegotiateProposal(piID string, msg *ProposeCredential) error { 222 return c.service.ActionContinue(piID, WithProposeCredential(msg)) 223 } 224 225 // AcceptRequest is used when the Issuer is willing to accept the request. 226 // NOTE: For async usage. 227 func (c *Client) AcceptRequest(piID string, msg *IssueCredential) error { 228 return c.service.ActionContinue(piID, WithIssueCredential(msg)) 229 } 230 231 // DeclineProposal is used when the Issuer does not want to accept the proposal. 232 // NOTE: For async usage. 233 func (c *Client) DeclineProposal(piID, reason string, options ...IssuerDeclineOptions) error { 234 return c.service.ActionStop(piID, errors.New(reason), prepareRedirectProperties(webRedirectStatusFAIL, options...)) 235 } 236 237 // DeclineOffer is used when the Holder does not want to accept the offer. 238 // NOTE: For async usage. 239 func (c *Client) DeclineOffer(piID, reason string) error { 240 return c.service.ActionStop(piID, errors.New(reason)) 241 } 242 243 // DeclineRequest is used when the Issuer does not want to accept the request. 244 // NOTE: For async usage. 245 func (c *Client) DeclineRequest(piID, reason string, options ...IssuerDeclineOptions) error { 246 return c.service.ActionStop(piID, errors.New(reason), prepareRedirectProperties(webRedirectStatusFAIL, options...)) 247 } 248 249 // AcceptCredential is used when the Holder is willing to accept the IssueCredential. 250 // NOTE: For async usage. 251 func (c *Client) AcceptCredential(piID string, options ...AcceptCredentialOptions) error { 252 opts := &acceptCredentialOpts{} 253 254 for _, option := range options { 255 option(opts) 256 } 257 258 properties := map[string]interface{}{} 259 260 if opts.skipStore { 261 properties[issuecredentialmiddleware.SkipCredentialSaveKey] = true 262 } 263 264 return c.service.ActionContinue(piID, WithFriendlyNames(opts.names...), issuecredential.WithProperties(properties)) 265 } 266 267 // DeclineCredential is used when the Holder does not want to accept the IssueCredential. 268 // NOTE: For async usage. 269 func (c *Client) DeclineCredential(piID, reason string) error { 270 return c.service.ActionStop(piID, errors.New(reason)) 271 } 272 273 // AcceptProblemReport accepts problem report action. 274 func (c *Client) AcceptProblemReport(piID string) error { 275 return c.service.ActionContinue(piID) 276 } 277 278 // WithProposeCredential allows providing ProposeCredential message 279 // USAGE: This message should be provided after receiving an OfferCredential message. 280 func WithProposeCredential(msg *ProposeCredential) issuecredential.Opt { 281 origin := *msg 282 283 return issuecredential.WithProposeCredential(&origin) 284 } 285 286 // WithRequestCredential allows providing RequestCredential message 287 // USAGE: This message should be provided after receiving an OfferCredential message. 288 func WithRequestCredential(msg *RequestCredential) issuecredential.Opt { 289 origin := *msg 290 291 return issuecredential.WithRequestCredential(&origin) 292 } 293 294 // WithOfferCredential allows providing OfferCredential message 295 // USAGE: This message should be provided after receiving a ProposeCredential message. 296 func WithOfferCredential(msg *OfferCredential) issuecredential.Opt { 297 origin := *msg 298 299 return issuecredential.WithOfferCredential(&origin) 300 } 301 302 // WithIssueCredential allows providing IssueCredential message 303 // USAGE: This message should be provided after receiving a RequestCredential message. 304 func WithIssueCredential(msg *IssueCredential) issuecredential.Opt { 305 origin := *msg 306 307 return issuecredential.WithIssueCredential(&origin) 308 } 309 310 // WithFriendlyNames allows providing names for the credentials. 311 // USAGE: This function should be used when the Holder receives IssueCredential message. 312 func WithFriendlyNames(names ...string) issuecredential.Opt { 313 return issuecredential.WithFriendlyNames(names...) 314 } 315 316 // acceptCredentialOpts options for accepting credential in holder. 317 type acceptCredentialOpts struct { 318 names []string 319 skipStore bool 320 } 321 322 // AcceptCredentialOptions is custom option for accepting credential in holder. 323 type AcceptCredentialOptions func(opts *acceptCredentialOpts) 324 325 // AcceptByFriendlyNames option to provide optional friendly names for accepting credentials. 326 func AcceptByFriendlyNames(names ...string) AcceptCredentialOptions { 327 return func(opts *acceptCredentialOpts) { 328 opts.names = names 329 } 330 } 331 332 // AcceptBySkippingStorage skips storing incoming credential to storage. 333 func AcceptBySkippingStorage() AcceptCredentialOptions { 334 return func(opts *acceptCredentialOpts) { 335 opts.skipStore = true 336 } 337 } 338 339 // redirectOpts options for web redirect information to holder from issuer. 340 type redirectOpts struct { 341 redirect string 342 } 343 344 // IssuerDeclineOptions is custom option for sending web redirect options to holder. 345 // https://github.com/hyperledger/aries-rfcs/tree/main/concepts/0700-oob-through-redirect 346 type IssuerDeclineOptions func(opts *redirectOpts) 347 348 // RequestRedirect option to provide optional redirect URL requesting holder to redirect. 349 func RequestRedirect(url string) IssuerDeclineOptions { 350 return func(opts *redirectOpts) { 351 opts.redirect = url 352 } 353 } 354 355 // create web redirect properties to add ~web-redirect decorator. 356 func prepareRedirectProperties(status string, options ...IssuerDeclineOptions) issuecredential.Opt { 357 properties := map[string]interface{}{} 358 359 opts := &redirectOpts{} 360 361 for _, option := range options { 362 option(opts) 363 } 364 365 if opts.redirect != "" { 366 properties[webRedirectDecorator] = &decorator.WebRedirect{ 367 Status: status, 368 URL: opts.redirect, 369 } 370 } 371 372 return issuecredential.WithProperties(properties) 373 }