Skip to content

Commit

Permalink
Docusign Webhook Payload
Browse files Browse the repository at this point in the history
- Updated payload to handle json
- Created payload models for the webhook endpoint

Signed-off-by: Harold Wanyama <[email protected]>
  • Loading branch information
nickmango committed Nov 23, 2023
1 parent 24e4efd commit 4411a37
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 118 deletions.
2 changes: 0 additions & 2 deletions cla-backend-go/swagger/cla.v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4161,8 +4161,6 @@ paths:
description: Receives XML data when an individual signs a document in DocuSign.
security: [ ]
operationId: iclaCallbackGithub
consumes:
- application/xml
parameters:
- $ref: "#/parameters/x-request-id"
- name: installation_id
Expand Down
26 changes: 7 additions & 19 deletions cla-backend-go/v2/sign/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ package sign
import (
"context"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"strings"

log "github.com/communitybridge/easycla/cla-backend-go/logging"
Expand Down Expand Up @@ -128,34 +126,24 @@ func Configure(api *operations.EasyclaAPI, service Service, userService users.Se
func(params sign.IclaCallbackGithubParams) middleware.Responder {
reqId := utils.GetRequestID(params.XREQUESTID)
ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTIDKey, reqId)

f := logrus.Fields{
"functionName": "v2.sign.handlers.SignIclaCallbackGithubHandler",
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
}
log.WithFields(f).Debug("github callback")

log.WithFields(f).Debugf("body: %+v", params.Body)

body, err := io.ReadAll(params.HTTPRequest.Body)
if err != nil {
log.WithFields(f).WithError(err).Warn("unable to read github callback body")
jsonBytes, marshalErr := json.Marshal(params.Body)
if marshalErr != nil {
log.WithFields(f).WithError(marshalErr).Warn("unable to marshal github callback body")
return sign.NewIclaCallbackGithubBadRequest()
}

var data DocuSignXMLData
err = xml.Unmarshal(body, &data)

err := service.SignedIndividualCallbackGithub(ctx, jsonBytes, params.InstallationID, params.ChangeRequestID, params.GithubRepositoryID)
if err != nil {
log.WithFields(f).WithError(err).Warn("unable to unmarshal github callback body")
return sign.NewIclaCallbackGithubBadRequest()
}

log.WithFields(f).Debugf("data: %+v", data)

// err := service.SignedIndividualCallbackGithub(ctx, payload, params.InstallationID, params.ChangeRequestID, params.GithubRepositoryID)
// if err != nil {
// return sign.NewIclaCallbackGithubBadRequest()
// }
log.WithFields(f).Debug("github callback")
// err := service.SignedIndividualCallbackGithub(ctx, payload, params.UserID)
return sign.NewCclaCallbackOK()
})

Expand Down
127 changes: 124 additions & 3 deletions cla-backend-go/v2/sign/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,9 +331,19 @@ type DocuSignRecipientEvent struct {
}

type DocuSignEventNotification struct {
URL string `json:"url"`
LoggingEnabled bool `json:"loggingEnabled"`
EnvelopeEvents []DocuSignRecipientEvent `json:"envelopeEvents"`
URL string `json:"url"`
LoggingEnabled bool `json:"loggingEnabled"`
RequireAcknowledgment string `json:"requireAcknowledgment"`
UseSoapInterface string `json:"useSoapInterface"`
IncludeCertificateWithSoap string `json:"includeCertificateWithSoap"`
SignMessageWithX509Cert string `json:"signMessageWithX509Cert"`
IncludeDocuments string `json:"includeDocuments"`
IncludeEnvelopeVoidReason string `json:"includeEnvelopeVoidReason"`
IncludeTimeZone string `json:"includeTimeZone"`
IncludeSenderAccountAsCustomField string `json:"includeSenderAccountAsCustomField"`
IncludeDocumentFields string `json:"includeDocumentFields"`
IncludeCertificateOfCompletion string `json:"includeCertificateOfCompletion"`
EnvelopeEvents []DocuSignRecipientEvent `json:"envelopeEvents"`
}

type Recipient struct {
Expand Down Expand Up @@ -400,3 +410,114 @@ type DocusignRecipientView struct {
type DocusignRecipientViewResponse struct {
URL string `json:"url"`
}

type Payload struct {
APIVersion string `json:"apiVersion"`
ConfigurationID int `json:"configurationId"`
Data Data `json:"data"`
Event string `json:"event"`
GeneratedDateTime string `json:"generatedDateTime"`
RetryCount int `json:"retryCount"`
URI string `json:"uri"`
}

type Data struct {
AccountID string `json:"accountId"`
EnvelopeID string `json:"envelopeId"`
EnvelopeSummary EnvelopeSummary `json:"envelopeSummary"`
UserID string `json:"userId"`
}

type EnvelopeSummary struct {
AllowComments string `json:"allowComments"`
AllowMarkup string `json:"allowMarkup"`
AllowReassign string `json:"allowReassign"`
AllowViewHistory string `json:"allowViewHistory"`
AnySigner interface{} `json:"anySigner"`
AttachmentsURI string `json:"attachmentsUri"`
AutoNavigation string `json:"autoNavigation"`
BurnDefaultTabData string `json:"burnDefaultTabData"`
CertificateURI string `json:"certificateUri"`
CompletedDateTime string `json:"completedDateTime"`
CreatedDateTime string `json:"createdDateTime"`
CustomFieldsURI string `json:"customFieldsUri"`
DeliveredDateTime string `json:"deliveredDateTime"`
DocumentsCombinedURI string `json:"documentsCombinedUri"`
DocumentsURI string `json:"documentsUri"`
EmailBlurb string `json:"emailBlurb"`
EmailSubject string `json:"emailSubject"`
EnableWetSign string `json:"enableWetSign"`
EnvelopeID string `json:"envelopeId"`
EnvelopeIdStamping string `json:"envelopeIdStamping"`
EnvelopeLocation string `json:"envelopeLocation"`
EnvelopeMetadata EnvelopeMetadata `json:"envelopeMetadata"`
EnvelopeURI string `json:"envelopeUri"`
ExpireAfter string `json:"expireAfter"`
ExpireDateTime string `json:"expireDateTime"`
ExpireEnabled string `json:"expireEnabled"`
HasComments string `json:"hasComments"`
HasFormDataChanged string `json:"hasFormDataChanged"`
InitialSentDateTime string `json:"initialSentDateTime"`
Is21CFRPart11 string `json:"is21CFRPart11"`
IsDynamicEnvelope string `json:"isDynamicEnvelope"`
IsSignatureProviderEnvelope string `json:"isSignatureProviderEnvelope"`
LastModifiedDateTime string `json:"lastModifiedDateTime"`
NotificationURI string `json:"notificationUri"`
PurgeState string `json:"purgeState"`
Recipients Recipients `json:"recipients"`
RecipientsURI string `json:"recipientsUri"`
Sender Sender `json:"sender"`
SentDateTime string `json:"sentDateTime"`
SignerCanSignOnMobile string `json:"signerCanSignOnMobile"`
SigningLocation string `json:"signingLocation"`
Status string `json:"status"`
StatusChangedDateTime string `json:"statusChangedDateTime"`
TemplatesURI string `json:"templatesUri"`
}

type EnvelopeMetadata struct {
AllowAdvancedCorrect string `json:"allowAdvancedCorrect"`
AllowCorrect string `json:"allowCorrect"`
EnableSignWithNotary string `json:"enableSignWithNotary"`
}

type Recipients struct {
Agents []interface{} `json:"agents"` // <nil>
CarbonCopies []interface{} `json:"carbonCopies"` // <nil>
CertifiedDeliveries []interface{} `json:"certifiedDeliveries"` // <nil>
CurrentRoutingOrder string `json:"currentRoutingOrder"` // 1
Editors []interface{} `json:"editors"` // <nil>
InPersonSigners []interface{} `json:"inPersonSigners"` // <nil>
Intermediaries []interface{} `json:"intermediaries"` // <nil>
Notaries []interface{} `json:"notaries"` // <nil>
RecipientCount string `json:"recipientCount"` // 1
Seals []interface{} `json:"seals"` // <nil>
Signers []WebhookSigner `json:"signers"`
Witnesses []interface{} `json:"witnesses"` // <nil>
}

type WebhookSigner struct {
CompletedCount string `json:"completedCount"` // 0
CreationReason string `json:"creationReason"` // sender
DeliveryMethod string `json:"deliveryMethod"` // email
Email string `json:"email"` // test@test
IsBulkRecipient string `json:"isBulkRecipient"` // "false"
Name string `json:"name"` // Test DocuSign
RecipientID string `json:"recipientId"` // 1
RecipientIDGuid string `json:"recipientIdGuid"` // 9fd66d5d-7396-4b80-a85e-2a7e536471b1
ReceipientType string `json:"recipientType"` // signer
RequireIdLookup string `json:"requireIdLookup"` // "false"
RequireUploadSignature string `json:"requireUploadSignature"` // "false"
RoutingOrder string `json:"routingOrder"` // 1
SentDateTime time.Time `json:"sentDateTime"` // 2023-05-26T18:55:48.257Z
Status string `json:"status"` // sent
UserId string `json:"userId"` // 9fd66d5d-7396-4b80-a85e-2a7e536471b1
}

type Sender struct {
AccountID string `json:"accountId"` // 9fd66d5d-7396-4b80-a85e-2a7e536471b1
Email string `json:"email"` // test@test
IPAddress string `json:"ipAddress"` // 35.11.11.111
UserName string `json:"userName"` // Test DocuSign
UserID string `json:"userId"` // 9fd66d5d-7396-4b80-a85e-2a7e536471b1
}
106 changes: 12 additions & 94 deletions cla-backend-go/v2/sign/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package sign
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -356,100 +357,15 @@ func (s *service) SignedIndividualCallbackGithub(ctx context.Context, payload []

log.WithFields(f).Debug("processing signed individual callback...")

// """
// Will be called on ICLA signature callback, but also when a document has been
// opened by a user - no action required then.
// """
// fn = 'models.docusign_models.signed_individual_callback'
// cla.log.debug(f'{fn} - Docusign ICLA signed callback POST data: {content}')
// tree = ET.fromstring(content)
// # Get envelope ID.
// envelope_id = tree.find('.//' + self.TAGS['envelope_id']).text
// # Assume only one signature per signature.
// signature_id = tree.find('.//' + self.TAGS['client_user_id']).text
// signature = cla.utils.get_signature_instance()
// try:
// signature.load(signature_id)
// except DoesNotExist:
// cla.log.error(f'{fn} - DocuSign ICLA callback returned signed info on '
// f'invalid signature: {content}')
// return
// # Iterate through recipients and update the signature signature status if changed.
// elem = tree.find('.//' + self.TAGS['recipient_statuses'] + '/' + self.TAGS['recipient_status'])
// status = elem.find(self.TAGS['status']).text
// if status == 'Completed' and not signature.get_signature_signed():
// cla.log.info(f'{fn} - ICLA signature signed ({signature_id}) - '
// 'Notifying repository service provider')
// signature.set_signature_signed(True)
// populate_signature_from_icla_callback(content, tree, signature)
// # Save signature
// signature.save()

// # Update the repository provider with this change - this will update the comment (if necessary)
// # and the status - do this early in the flow as the user will be immediately redirected back
// update_repository_provider(installation_id, github_repository_id, change_request_id)
// # Send user their signed document.
// user = User()
// user.load(signature.get_signature_reference_id())
// # Update user name in case is empty.
// if not user.get_user_name():
// full_name_field = tree.find(".//*[@name='full_name']")
// if full_name_field is not None:
// full_name = full_name_field.find(self.TAGS['field_value'])
// if full_name:
// cla.log.info(f'{fn} - updating user: {user.get_user_github_id()} with name : {full_name.text}')
// user.set_user_name(full_name.text)
// user.save()
// else:
// cla.log.warning(f'{fn} - unable to locate full_name value in the docusign callback - '
// f'unable to update user record.')
// # Remove the active signature metadata.
// cla.utils.delete_active_signature_metadata(user.get_user_id())
// # Get signed document
// document_data = self.get_signed_document(envelope_id, user)
// # Send email with signed document.
// self.send_signed_document(signature, document_data, user, icla=True)

// # Verify user id exist for saving on storage
// user_id = user.get_user_id()
// if user_id is None:
// cla.log.warning(f'{fn} - missing user_id on ICLA for saving signed file on s3 storage.')
// raise SigningError('Missing user_id on ICLA for saving signed file on s3 storage.')

// # Store document on S3
// project_id = signature.get_signature_project_id()
// self.send_to_s3(document_data, project_id, signature_id, 'icla', user_id)

// # Log the event
// try:
// # Load the Project by ID and send audit event
// cla.log.debug(f'{fn} - creating an event log entry for event_type: {EventType.IndividualSignatureSigned}')
// project = Project()
// project.load(signature.get_signature_project_id())
// event_data = (f'The user {user.get_user_name()} signed an individual CLA for '
// f'project {project.get_project_name()}.')
// event_summary = (f'The user {user.get_user_name()} signed an individual CLA for '
// f'project {project.get_project_name()} with project ID: {project.get_project_id()}.')
// Event.create_event(
// event_type=EventType.IndividualSignatureSigned,
// event_cla_group_id=signature.get_signature_project_id(),
// event_company_id=None,
// event_user_id=signature.get_signature_reference_id(),
// event_user_name=user.get_user_name() if user else None,
// event_data=event_data,
// event_summary=event_summary,
// contains_pii=False,
// )
// cla.log.debug(f'{fn} - created an event log entry for event_type: {EventType.IndividualSignatureSigned}')
// except DoesNotExist as err:
// msg = (f'{fn} - unable to load project by CLA Group ID: {signature.get_signature_project_id()}, '
// f'unable to send audit event, error: {err}')
// cla.log.warning(msg)
// return

// log.WithFields(f).Debugf("Docusign ICLA signed callback POST data: %s", body)

//parse xml content
var dataModel Payload

err := json.Unmarshal(payload, &dataModel)
if err != nil {
log.WithFields(f).WithError(err).Warn("unable to unmarshall payload")
return err
}

log.WithFields(f).Debugf("webhook payload: %+v", dataModel)

return nil

Expand Down Expand Up @@ -1071,6 +987,8 @@ func (s *service) populateSignURL(ctx context.Context,
URL: callbackURL,
LoggingEnabled: true,
EnvelopeEvents: recipientEvents,
UseSoapInterface: "true",
IncludeDocuments: "true",
}

envelopeRequest = DocuSignEnvelopeRequest{
Expand Down

0 comments on commit 4411a37

Please sign in to comment.