|
8 | 8 | "context" |
9 | 9 | "encoding/base64" |
10 | 10 | "encoding/json" |
| 11 | + "encoding/xml" |
11 | 12 | "errors" |
12 | 13 | "fmt" |
13 | 14 | "html" |
@@ -9151,6 +9152,45 @@ func (h *Handlers) CheckAndPrepareEmployeeSignatureV2(w http.ResponseWriter, r * |
9151 | 9152 | respond.JSON(w, http.StatusOK, map[string]any{"success": []string{"the employee is ready to sign the CCLA"}}) |
9152 | 9153 | } |
9153 | 9154 |
|
| 9155 | +type docusignCallbackEnvelope struct { |
| 9156 | + EnvelopeStatus struct { |
| 9157 | + RecipientStatuses []struct { |
| 9158 | + ClientUserID string `xml:"ClientUserId"` |
| 9159 | + Status string `xml:"Status"` |
| 9160 | + } `xml:"RecipientStatuses>RecipientStatus"` |
| 9161 | + } `xml:"EnvelopeStatus"` |
| 9162 | +} |
| 9163 | + |
| 9164 | +func extractDocuSignSignatureCompletion(payload []byte) (string, bool) { |
| 9165 | + var envelope docusignCallbackEnvelope |
| 9166 | + if err := xml.Unmarshal(payload, &envelope); err != nil { |
| 9167 | + return "", false |
| 9168 | + } |
| 9169 | + if len(envelope.EnvelopeStatus.RecipientStatuses) == 0 { |
| 9170 | + return "", false |
| 9171 | + } |
| 9172 | + recipient := envelope.EnvelopeStatus.RecipientStatuses[0] |
| 9173 | + signatureID := strings.TrimSpace(recipient.ClientUserID) |
| 9174 | + completed := strings.EqualFold(strings.TrimSpace(recipient.Status), "Completed") |
| 9175 | + return signatureID, completed |
| 9176 | +} |
| 9177 | + |
| 9178 | +func (h *Handlers) waitForSignedSignature(ctx context.Context, signatureID string, attempts int, delay time.Duration) bool { |
| 9179 | + if h == nil || h.signatures == nil || strings.TrimSpace(signatureID) == "" { |
| 9180 | + return false |
| 9181 | + } |
| 9182 | + for i := 0; i < attempts; i++ { |
| 9183 | + sig, found, err := h.signatures.GetByID(ctx, signatureID) |
| 9184 | + if err == nil && found && getAttrBool(sig, "signature_signed") { |
| 9185 | + return true |
| 9186 | + } |
| 9187 | + if i+1 < attempts { |
| 9188 | + time.Sleep(delay) |
| 9189 | + } |
| 9190 | + } |
| 9191 | + return false |
| 9192 | +} |
| 9193 | + |
9154 | 9194 | // POST /v2/signed/individual/{installation_id}/{github_repository_id}/{change_request_id} |
9155 | 9195 | // Python: cla/routes.py:1884 post_individual_signed() |
9156 | 9196 | // Calls: cla.controllers.signing.post_individual_signed |
@@ -9189,6 +9229,21 @@ func (h *Handlers) PostIndividualSignedV2(w http.ResponseWriter, r *http.Request |
9189 | 9229 | if status >= 400 { |
9190 | 9230 | logging.Warnf("v4 signed/individual returned %d: %s", status, string(respBody)) |
9191 | 9231 | } |
| 9232 | + if signatureID, completed := extractDocuSignSignatureCompletion(body); completed && signatureID != "" { |
| 9233 | + if h.waitForSignedSignature(r.Context(), signatureID, 10, 500*time.Millisecond) { |
| 9234 | + if err := h.triggerGitHubChangeRequestUpdateV4( |
| 9235 | + r.Context(), |
| 9236 | + strings.TrimSpace(chi.URLParam(r, "installation_id")), |
| 9237 | + strings.TrimSpace(chi.URLParam(r, "github_repository_id")), |
| 9238 | + strings.TrimSpace(chi.URLParam(r, "change_request_id")), |
| 9239 | + ); err != nil { |
| 9240 | + logging.Warnf("post_individual_signed - best-effort GitHub change request refresh failed: %v", err) |
| 9241 | + } |
| 9242 | + } else { |
| 9243 | + logging.Warnf("post_individual_signed - signed signature did not become visible in time: %s", signatureID) |
| 9244 | + } |
| 9245 | + } |
| 9246 | + |
9192 | 9247 | copyV4ResponseHeaders(w, hdr) |
9193 | 9248 | w.WriteHeader(http.StatusOK) |
9194 | 9249 | if len(respBody) == 0 { |
|
0 commit comments