github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/protocol/issuecredential/states.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  	"fmt"
    12  
    13  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/model"
    14  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
    15  )
    16  
    17  const (
    18  	// common states.
    19  	stateNameStart      = "start"
    20  	stateNameAbandoning = "abandoning"
    21  	stateNameDone       = "done"
    22  	stateNameNoop       = "noop"
    23  
    24  	// states for Issuer.
    25  	stateNameProposalReceived = "proposal-received"
    26  	stateNameOfferSent        = "offer-sent"
    27  	stateNameRequestReceived  = "request-received"
    28  	stateNameCredentialIssued = "credential-issued"
    29  
    30  	// states for Holder.
    31  	stateNameProposalSent       = "proposal-sent"
    32  	stateNameOfferReceived      = "offer-received"
    33  	stateNameRequestSent        = "request-sent"
    34  	stateNameCredentialReceived = "credential-received"
    35  
    36  	// web redirect decorator V2.
    37  	webRedirect = "~web-redirect"
    38  )
    39  
    40  const (
    41  	codeRejectedError = "rejected"
    42  	codeInternalError = "internal"
    43  )
    44  
    45  // state action for network call.
    46  type stateAction func(messenger service.Messenger) error
    47  
    48  // the protocol's state.
    49  type state interface {
    50  	// Name of this state.
    51  	Name() string
    52  	// CanTransitionTo Whether this state allows transitioning into the next state.
    53  	CanTransitionTo(next state) bool
    54  	// ExecuteInbound this state, returning a followup state to be immediately executed as well.
    55  	// The 'noOp' state should be returned if the state has no followup.
    56  	ExecuteInbound(msg *MetaData) (state, stateAction, error)
    57  	ExecuteOutbound(msg *MetaData) (state, stateAction, error)
    58  	// Message properties required for further steps or next state transition.
    59  	Properties() map[string]interface{}
    60  }
    61  
    62  // represents zero state's action.
    63  func zeroAction(service.Messenger) error { return nil }
    64  
    65  // noOp state.
    66  type noOp struct{}
    67  
    68  func (s *noOp) Name() string {
    69  	return stateNameNoop
    70  }
    71  
    72  func (s *noOp) CanTransitionTo(_ state) bool {
    73  	return false
    74  }
    75  
    76  func (s *noOp) ExecuteInbound(_ *MetaData) (state, stateAction, error) {
    77  	return nil, nil, errors.New("cannot execute no-op")
    78  }
    79  
    80  func (s *noOp) ExecuteOutbound(_ *MetaData) (state, stateAction, error) {
    81  	return nil, nil, errors.New("cannot execute no-op")
    82  }
    83  
    84  func (s *noOp) Properties() map[string]interface{} {
    85  	return map[string]interface{}{}
    86  }
    87  
    88  // start state.
    89  type start struct{}
    90  
    91  func (s *start) Name() string {
    92  	return stateNameStart
    93  }
    94  
    95  func (s *start) CanTransitionTo(st state) bool {
    96  	switch st.Name() {
    97  	// Issuer.
    98  	case stateNameProposalReceived, stateNameOfferSent, stateNameRequestReceived:
    99  		return true
   100  	// Holder.
   101  	case stateNameProposalSent, stateNameOfferReceived, stateNameRequestSent:
   102  		return true
   103  	}
   104  
   105  	return false
   106  }
   107  
   108  func (s *start) ExecuteInbound(_ *MetaData) (state, stateAction, error) {
   109  	return nil, nil, fmt.Errorf("%s: ExecuteInbound is not implemented yet", s.Name())
   110  }
   111  
   112  func (s *start) ExecuteOutbound(_ *MetaData) (state, stateAction, error) {
   113  	return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name())
   114  }
   115  
   116  func (s *start) Properties() map[string]interface{} {
   117  	return map[string]interface{}{}
   118  }
   119  
   120  // abandoning state.
   121  type abandoning struct {
   122  	V          string
   123  	Code       string
   124  	properties map[string]interface{}
   125  }
   126  
   127  func (s *abandoning) Name() string {
   128  	return stateNameAbandoning
   129  }
   130  
   131  func (s *abandoning) CanTransitionTo(st state) bool {
   132  	return st.Name() == stateNameDone
   133  }
   134  
   135  func (s *abandoning) ExecuteInbound(md *MetaData) (state, stateAction, error) {
   136  	// if code is not provided it means we do not need to notify the another agent.
   137  	// if we received ProblemReport message no need to answer.
   138  	if s.Code == "" || md.Msg.Type() == ProblemReportMsgTypeV2 || md.Msg.Type() == ProblemReportMsgTypeV3 {
   139  		return &done{}, zeroAction, nil
   140  	}
   141  
   142  	code := model.Code{Code: s.Code}
   143  
   144  	// if the protocol was stopped by the user we will set the rejected error code.
   145  	if errors.As(md.err, &customError{}) {
   146  		code = model.Code{Code: codeRejectedError}
   147  	}
   148  
   149  	thID, err := md.Msg.ThreadID()
   150  	if err != nil {
   151  		return nil, nil, fmt.Errorf("threadID: %w", err)
   152  	}
   153  
   154  	return &done{}, func(messenger service.Messenger) error {
   155  		if s.V == SpecV3 {
   156  			return messenger.ReplyToNested(service.NewDIDCommMsgMap(&model.ProblemReportV2{
   157  				Type: ProblemReportMsgTypeV3,
   158  				Body: model.ProblemReportV2Body{Code: code.Code, WebRedirect: md.properties[webRedirect]},
   159  			}), &service.NestedReplyOpts{ThreadID: thID, MyDID: md.MyDID, TheirDID: md.TheirDID, V: getDIDVersion(s.V)})
   160  		}
   161  
   162  		return messenger.ReplyToNested(service.NewDIDCommMsgMap(&model.ProblemReport{
   163  			Type:        ProblemReportMsgTypeV2,
   164  			Description: code,
   165  			WebRedirect: md.properties[webRedirect],
   166  		}), &service.NestedReplyOpts{ThreadID: thID, MyDID: md.MyDID, TheirDID: md.TheirDID, V: getDIDVersion(s.V)})
   167  	}, nil
   168  }
   169  
   170  func (s *abandoning) ExecuteOutbound(_ *MetaData) (state, stateAction, error) {
   171  	return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name())
   172  }
   173  
   174  func (s *abandoning) Properties() map[string]interface{} {
   175  	return s.properties
   176  }
   177  
   178  // done state.
   179  type done struct {
   180  	V          string
   181  	properties map[string]interface{}
   182  }
   183  
   184  func (s *done) Name() string {
   185  	return stateNameDone
   186  }
   187  
   188  func (s *done) CanTransitionTo(_ state) bool {
   189  	return false
   190  }
   191  
   192  func (s *done) ExecuteInbound(_ *MetaData) (state, stateAction, error) {
   193  	return &noOp{}, zeroAction, nil
   194  }
   195  
   196  func (s *done) ExecuteOutbound(_ *MetaData) (state, stateAction, error) {
   197  	return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name())
   198  }
   199  
   200  func (s *done) Properties() map[string]interface{} {
   201  	return s.properties
   202  }
   203  
   204  // proposalReceived the Issuer's state.
   205  type proposalReceived struct {
   206  	V          string
   207  	properties map[string]interface{}
   208  }
   209  
   210  func (s *proposalReceived) Name() string {
   211  	return stateNameProposalReceived
   212  }
   213  
   214  func (s *proposalReceived) CanTransitionTo(st state) bool {
   215  	return st.Name() == stateNameOfferSent || st.Name() == stateNameAbandoning
   216  }
   217  
   218  func (s *proposalReceived) ExecuteInbound(_ *MetaData) (state, stateAction, error) {
   219  	return &offerSent{V: s.V}, zeroAction, nil
   220  }
   221  
   222  func (s *proposalReceived) ExecuteOutbound(_ *MetaData) (state, stateAction, error) {
   223  	return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name())
   224  }
   225  
   226  func (s *proposalReceived) Properties() map[string]interface{} {
   227  	return s.properties
   228  }
   229  
   230  // offerSent the Issuer's state.
   231  type offerSent struct {
   232  	V          string
   233  	properties map[string]interface{}
   234  }
   235  
   236  func (s *offerSent) Name() string {
   237  	return stateNameOfferSent
   238  }
   239  
   240  func (s *offerSent) CanTransitionTo(st state) bool {
   241  	return st.Name() == stateNameProposalReceived ||
   242  		st.Name() == stateNameRequestReceived ||
   243  		st.Name() == stateNameAbandoning
   244  }
   245  
   246  func (s *offerSent) ExecuteInbound(md *MetaData) (state, stateAction, error) {
   247  	if md.offerCredentialV2 == nil && md.offerCredentialV3 == nil {
   248  		return nil, nil, errors.New("offer credential was not provided")
   249  	}
   250  
   251  	// creates the state's action.
   252  	action := func(messenger service.Messenger) error {
   253  		if s.V == SpecV3 {
   254  			// sets message type
   255  			md.offerCredentialV3.Type = OfferCredentialMsgTypeV3
   256  
   257  			return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.offerCredentialV3), md.MyDID, md.TheirDID,
   258  				service.WithVersion(getDIDVersion(s.V)))
   259  		}
   260  
   261  		// sets message type.
   262  		md.offerCredentialV2.Type = OfferCredentialMsgTypeV2
   263  
   264  		return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.offerCredentialV2), md.MyDID, md.TheirDID,
   265  			service.WithVersion(getDIDVersion(s.V)))
   266  	}
   267  
   268  	return &noOp{}, action, nil
   269  }
   270  
   271  func (s *offerSent) ExecuteOutbound(md *MetaData) (state, stateAction, error) {
   272  	// creates the state's action.
   273  	action := func(messenger service.Messenger) error {
   274  		return messenger.Send(md.Msg, md.MyDID, md.TheirDID, service.WithVersion(getDIDVersion(s.V)))
   275  	}
   276  
   277  	return &noOp{}, action, nil
   278  }
   279  
   280  func (s *offerSent) Properties() map[string]interface{} {
   281  	return s.properties
   282  }
   283  
   284  // requestReceived the Issuer's state.
   285  type requestReceived struct {
   286  	V          string
   287  	properties map[string]interface{}
   288  }
   289  
   290  func (s *requestReceived) Name() string {
   291  	return stateNameRequestReceived
   292  }
   293  
   294  func (s *requestReceived) CanTransitionTo(st state) bool {
   295  	return st.Name() == stateNameCredentialIssued || st.Name() == stateNameAbandoning
   296  }
   297  
   298  func (s *requestReceived) ExecuteInbound(md *MetaData) (state, stateAction, error) {
   299  	if md.issueCredentialV2 == nil && md.issueCredentialV3 == nil {
   300  		return nil, nil, errors.New("issue credential was not provided")
   301  	}
   302  
   303  	// creates the state's action
   304  	action := func(messenger service.Messenger) error {
   305  		if s.V == SpecV3 {
   306  			// sets message type
   307  			md.issueCredentialV3.Type = IssueCredentialMsgTypeV3
   308  
   309  			return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.issueCredentialV3), md.MyDID, md.TheirDID,
   310  				service.WithVersion(getDIDVersion(s.V)))
   311  		}
   312  
   313  		// sets message type
   314  		md.issueCredentialV2.Type = IssueCredentialMsgTypeV2
   315  
   316  		return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.issueCredentialV2), md.MyDID, md.TheirDID,
   317  			service.WithVersion(getDIDVersion(s.V)))
   318  	}
   319  
   320  	return &credentialIssued{}, action, nil
   321  }
   322  
   323  func (s *requestReceived) ExecuteOutbound(_ *MetaData) (state, stateAction, error) {
   324  	return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name())
   325  }
   326  
   327  func (s *requestReceived) Properties() map[string]interface{} {
   328  	return s.properties
   329  }
   330  
   331  // credentialIssued the Issuer's state.
   332  type credentialIssued struct {
   333  	V          string
   334  	properties map[string]interface{}
   335  }
   336  
   337  func (s *credentialIssued) Name() string {
   338  	return stateNameCredentialIssued
   339  }
   340  
   341  func (s *credentialIssued) CanTransitionTo(st state) bool {
   342  	return st.Name() == stateNameDone || st.Name() == stateNameAbandoning
   343  }
   344  
   345  func (s *credentialIssued) ExecuteInbound(_ *MetaData) (state, stateAction, error) {
   346  	return &noOp{}, zeroAction, nil
   347  }
   348  
   349  func (s *credentialIssued) ExecuteOutbound(_ *MetaData) (state, stateAction, error) {
   350  	return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name())
   351  }
   352  
   353  func (s *credentialIssued) Properties() map[string]interface{} {
   354  	return s.properties
   355  }
   356  
   357  // proposalSent the Holder's state.
   358  type proposalSent struct {
   359  	V          string
   360  	properties map[string]interface{}
   361  }
   362  
   363  func (s *proposalSent) Name() string {
   364  	return stateNameProposalSent
   365  }
   366  
   367  func (s *proposalSent) CanTransitionTo(st state) bool {
   368  	return st.Name() == stateNameOfferReceived || st.Name() == stateNameAbandoning
   369  }
   370  
   371  func (s *proposalSent) ExecuteInbound(md *MetaData) (state, stateAction, error) {
   372  	if md.proposeCredentialV2 == nil && md.proposeCredentialV3 == nil {
   373  		return nil, nil, errors.New("propose credential was not provided")
   374  	}
   375  
   376  	// creates the state's action
   377  	action := func(messenger service.Messenger) error {
   378  		if s.V == SpecV3 {
   379  			// sets message type
   380  			md.proposeCredentialV3.Type = ProposeCredentialMsgTypeV3
   381  
   382  			return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.proposeCredentialV3), md.MyDID, md.TheirDID,
   383  				service.WithVersion(getDIDVersion(s.V)))
   384  		}
   385  
   386  		// sets message type
   387  		md.proposeCredentialV2.Type = ProposeCredentialMsgTypeV2
   388  
   389  		return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(md.proposeCredentialV2), md.MyDID, md.TheirDID,
   390  			service.WithVersion(getDIDVersion(s.V)))
   391  	}
   392  
   393  	return &noOp{}, action, nil
   394  }
   395  
   396  func (s *proposalSent) ExecuteOutbound(md *MetaData) (state, stateAction, error) {
   397  	// creates the state's action
   398  	action := func(messenger service.Messenger) error {
   399  		return messenger.Send(md.Msg, md.MyDID, md.TheirDID, service.WithVersion(getDIDVersion(s.V)))
   400  	}
   401  
   402  	return &noOp{}, action, nil
   403  }
   404  
   405  func (s *proposalSent) Properties() map[string]interface{} {
   406  	return s.properties
   407  }
   408  
   409  // offerReceived the Holder's state.
   410  type offerReceived struct {
   411  	V          string
   412  	properties map[string]interface{}
   413  }
   414  
   415  func (s *offerReceived) Name() string {
   416  	return stateNameOfferReceived
   417  }
   418  
   419  func (s *offerReceived) CanTransitionTo(st state) bool {
   420  	return st.Name() == stateNameProposalSent ||
   421  		st.Name() == stateNameRequestSent ||
   422  		st.Name() == stateNameAbandoning
   423  }
   424  
   425  func (s *offerReceived) ExecuteInbound(md *MetaData) (state, stateAction, error) {
   426  	// sends propose credential if it was provided
   427  	if md.proposeCredentialV2 != nil || md.proposeCredentialV3 != nil {
   428  		return &proposalSent{V: s.V}, zeroAction, nil
   429  	}
   430  
   431  	var action func(messenger service.Messenger) error
   432  
   433  	if s.V == SpecV3 { //nolint: nestif
   434  		offer := OfferCredentialV3{}
   435  		if err := md.Msg.Decode(&offer); err != nil {
   436  			return nil, nil, fmt.Errorf("decode: %w", err)
   437  		}
   438  
   439  		response := &RequestCredentialV3{
   440  			Type: RequestCredentialMsgTypeV3,
   441  			ID:   offer.ID,
   442  			Body: RequestCredentialV3Body{
   443  				GoalCode: offer.Body.GoalCode,
   444  				Comment:  offer.Body.Comment,
   445  			},
   446  			Attachments: offer.Attachments,
   447  		}
   448  
   449  		req := md.RequestCredentialV3()
   450  		if req != nil && req.notEmpty() {
   451  			response = md.RequestCredentialV3()
   452  			response.Type = RequestCredentialMsgTypeV3
   453  		}
   454  
   455  		// creates the state's action
   456  		action = func(messenger service.Messenger) error {
   457  			return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(response), md.MyDID, md.TheirDID,
   458  				service.WithVersion(getDIDVersion(s.V)))
   459  		}
   460  	} else {
   461  		offer := OfferCredentialV2{}
   462  		if err := md.Msg.Decode(&offer); err != nil {
   463  			return nil, nil, fmt.Errorf("decode: %w", err)
   464  		}
   465  
   466  		response := &RequestCredentialV2{
   467  			Type:           RequestCredentialMsgTypeV2,
   468  			Formats:        offer.Formats,
   469  			RequestsAttach: offer.OffersAttach,
   470  		}
   471  
   472  		req := md.RequestCredentialV2()
   473  		if req != nil && req.notEmpty() {
   474  			response = md.RequestCredentialV2()
   475  			response.Type = RequestCredentialMsgTypeV2
   476  		}
   477  
   478  		// creates the state's action
   479  		action = func(messenger service.Messenger) error {
   480  			return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(response), md.MyDID, md.TheirDID,
   481  				service.WithVersion(getDIDVersion(s.V)))
   482  		}
   483  	}
   484  
   485  	return &requestSent{}, action, nil
   486  }
   487  
   488  func (s *offerReceived) ExecuteOutbound(_ *MetaData) (state, stateAction, error) {
   489  	return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name())
   490  }
   491  
   492  func (s *offerReceived) Properties() map[string]interface{} {
   493  	return s.properties
   494  }
   495  
   496  // requestSent the Holder's state.
   497  type requestSent struct {
   498  	V          string
   499  	properties map[string]interface{}
   500  }
   501  
   502  func (s *requestSent) Name() string {
   503  	return stateNameRequestSent
   504  }
   505  
   506  func (s *requestSent) CanTransitionTo(st state) bool {
   507  	return st.Name() == stateNameCredentialReceived || st.Name() == stateNameAbandoning
   508  }
   509  
   510  func (s *requestSent) ExecuteInbound(_ *MetaData) (state, stateAction, error) {
   511  	return &noOp{}, zeroAction, nil
   512  }
   513  
   514  func (s *requestSent) ExecuteOutbound(md *MetaData) (state, stateAction, error) {
   515  	// creates the state's action
   516  	action := func(messenger service.Messenger) error {
   517  		return messenger.Send(md.Msg, md.MyDID, md.TheirDID, service.WithVersion(getDIDVersion(s.V)))
   518  	}
   519  
   520  	return &noOp{}, action, nil
   521  }
   522  
   523  func (s *requestSent) Properties() map[string]interface{} {
   524  	return s.properties
   525  }
   526  
   527  // credentialReceived state.
   528  type credentialReceived struct {
   529  	V          string
   530  	properties map[string]interface{}
   531  }
   532  
   533  func (s *credentialReceived) Name() string {
   534  	return stateNameCredentialReceived
   535  }
   536  
   537  func (s *credentialReceived) CanTransitionTo(st state) bool {
   538  	return st.Name() == stateNameDone || st.Name() == stateNameAbandoning
   539  }
   540  
   541  func (s *credentialReceived) ExecuteInbound(md *MetaData) (state, stateAction, error) {
   542  	// creates the state's action
   543  	action := func(messenger service.Messenger) error {
   544  		if s.V == SpecV3 {
   545  			return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(model.AckV2{
   546  				Type: AckMsgTypeV3,
   547  				Body: model.AckV2Body{Status: "OK"},
   548  			}), md.MyDID, md.TheirDID, service.WithVersion(getDIDVersion(s.V)))
   549  		}
   550  
   551  		return messenger.ReplyToMsg(md.Msg, service.NewDIDCommMsgMap(model.Ack{
   552  			Type:   AckMsgTypeV2,
   553  			Status: "OK",
   554  		}), md.MyDID, md.TheirDID, service.WithVersion(getDIDVersion(s.V)))
   555  	}
   556  
   557  	return &done{properties: s.properties}, action, nil
   558  }
   559  
   560  func (s *credentialReceived) ExecuteOutbound(_ *MetaData) (state, stateAction, error) {
   561  	return nil, nil, fmt.Errorf("%s: ExecuteOutbound is not implemented yet", s.Name())
   562  }
   563  
   564  func (s *credentialReceived) Properties() map[string]interface{} {
   565  	return s.properties
   566  }