diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 32ec28c68..a15bbe795 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -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 diff --git a/cla-backend-go/v2/sign/handlers.go b/cla-backend-go/v2/sign/handlers.go index a948532d9..762320bfe 100644 --- a/cla-backend-go/v2/sign/handlers.go +++ b/cla-backend-go/v2/sign/handlers.go @@ -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" @@ -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() }) diff --git a/cla-backend-go/v2/sign/models.go b/cla-backend-go/v2/sign/models.go index 02bc01aae..dcefcc1ea 100644 --- a/cla-backend-go/v2/sign/models.go +++ b/cla-backend-go/v2/sign/models.go @@ -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 { @@ -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"` // + CarbonCopies []interface{} `json:"carbonCopies"` // + CertifiedDeliveries []interface{} `json:"certifiedDeliveries"` // + CurrentRoutingOrder string `json:"currentRoutingOrder"` // 1 + Editors []interface{} `json:"editors"` // + InPersonSigners []interface{} `json:"inPersonSigners"` // + Intermediaries []interface{} `json:"intermediaries"` // + Notaries []interface{} `json:"notaries"` // + RecipientCount string `json:"recipientCount"` // 1 + Seals []interface{} `json:"seals"` // + Signers []WebhookSigner `json:"signers"` + Witnesses []interface{} `json:"witnesses"` // +} + +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 +} diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index 7398c88e9..30e25dccb 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -6,6 +6,7 @@ package sign import ( "context" "encoding/base64" + "encoding/json" "errors" "fmt" "io" @@ -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 @@ -1071,6 +987,8 @@ func (s *service) populateSignURL(ctx context.Context, URL: callbackURL, LoggingEnabled: true, EnvelopeEvents: recipientEvents, + UseSoapInterface: "true", + IncludeDocuments: "true", } envelopeRequest = DocuSignEnvelopeRequest{