From a0fee800f2dd02a5665530f6b441d59ec5ce2f4e Mon Sep 17 00:00:00 2001 From: Harold Wanyama Date: Mon, 6 Nov 2023 14:34:50 +0300 Subject: [PATCH] Bug/Docusign Golang Integration - Fetched account id - Removed session call for github oauth token - Refactored document attachment on envelope creation Signed-off-by: Harold Wanyama --- cla-backend-go/cmd/server.go | 2 +- cla-backend-go/project/common/helpers.go | 1 + cla-backend-go/project/models/models.go | 25 +- cla-backend-go/signatures/repository.go | 72 ++- cla-backend-go/signatures/service.go | 6 +- cla-backend-go/swagger/common/signature.yaml | 2 + cla-backend-go/v2/sign/docusign.go | 461 +++++++++++++++++-- cla-backend-go/v2/sign/handlers.go | 111 +---- cla-backend-go/v2/sign/models.go | 66 ++- cla-backend-go/v2/sign/service.go | 129 +++--- cla-backend-go/v2/store/repository.go | 37 +- 11 files changed, 656 insertions(+), 256 deletions(-) diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index 56e4d108a..1853717bb 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -363,7 +363,7 @@ func server(localMode bool) http.Handler { v2Company.Configure(v2API, v2CompanyService, v1ProjectClaGroupRepo, configFile.LFXPortalURL, configFile.CorporateConsoleV1URL) cla_manager.Configure(api, v1ClaManagerService, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService) v2ClaManager.Configure(v2API, v2ClaManagerService, v1CompanyService, configFile.LFXPortalURL, configFile.CorporateConsoleV2URL, v1ProjectClaGroupRepo, userRepo) - sign.Configure(v2API, v2SignService) + sign.Configure(v2API, v2SignService, usersService) cla_groups.Configure(v2API, v2ClaGroupService, v1ProjectService, v1ProjectClaGroupRepo, eventsService) v2GithubActivity.Configure(v2API, v2GithubActivityService) diff --git a/cla-backend-go/project/common/helpers.go b/cla-backend-go/project/common/helpers.go index 111b393c8..20bace463 100644 --- a/cla-backend-go/project/common/helpers.go +++ b/cla-backend-go/project/common/helpers.go @@ -62,6 +62,7 @@ func BuildCLAGroupDocumentModels(dbDocumentModels []models2.DBProjectDocumentMod DocumentMajorVersion: dbDocumentModel.DocumentMajorVersion, DocumentMinorVersion: dbDocumentModel.DocumentMinorVersion, DocumentCreationDate: dbDocumentModel.DocumentCreationDate, + DocumentTabs: dbDocumentModel.DocumentTabs, }) } diff --git a/cla-backend-go/project/models/models.go b/cla-backend-go/project/models/models.go index f45f2f522..cf60318f2 100644 --- a/cla-backend-go/project/models/models.go +++ b/cla-backend-go/project/models/models.go @@ -3,6 +3,10 @@ package models +import ( + v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" +) + // DBProjectModel data model type DBProjectModel struct { DateCreated string `dynamodbav:"date_created"` @@ -28,14 +32,15 @@ type DBProjectModel struct { // DBProjectDocumentModel is a data model for the CLA Group Project documents type DBProjectDocumentModel struct { - DocumentName string `dynamodbav:"document_name"` - DocumentFileID string `dynamodbav:"document_file_id"` - DocumentPreamble string `dynamodbav:"document_preamble"` - DocumentLegalEntityName string `dynamodbav:"document_legal_entity_name"` - DocumentAuthorName string `dynamodbav:"document_author_name"` - DocumentContentType string `dynamodbav:"document_content_type"` - DocumentS3URL string `dynamodbav:"document_s3_url"` - DocumentMajorVersion string `dynamodbav:"document_major_version"` - DocumentMinorVersion string `dynamodbav:"document_minor_version"` - DocumentCreationDate string `dynamodbav:"document_creation_date"` + DocumentName string `dynamodbav:"document_name"` + DocumentFileID string `dynamodbav:"document_file_id"` + DocumentPreamble string `dynamodbav:"document_preamble"` + DocumentLegalEntityName string `dynamodbav:"document_legal_entity_name"` + DocumentAuthorName string `dynamodbav:"document_author_name"` + DocumentContentType string `dynamodbav:"document_content_type"` + DocumentS3URL string `dynamodbav:"document_s3_url"` + DocumentMajorVersion string `dynamodbav:"document_major_version"` + DocumentMinorVersion string `dynamodbav:"document_minor_version"` + DocumentCreationDate string `dynamodbav:"document_creation_date"` + DocumentTabs []v1Models.DocumentTab `dynamodbav:"document_tabs"` } diff --git a/cla-backend-go/signatures/repository.go b/cla-backend-go/signatures/repository.go index a50185442..922ace9ad 100644 --- a/cla-backend-go/signatures/repository.go +++ b/cla-backend-go/signatures/repository.go @@ -69,7 +69,7 @@ type SignatureRepository interface { DeleteGithubOrganizationFromApprovalList(ctx context.Context, signatureID, githubOrganizationID string) ([]models.GithubOrg, error) ValidateProjectRecord(ctx context.Context, signatureID, note string) error InvalidateProjectRecord(ctx context.Context, signatureID, note string) error - CreateOrUpdateSignature(ctx context.Context, signature *models.Signature) (*models.Signature, error) + UpdateEnvelopeDetails(ctx context.Context, signatureID, envelopeID string, signURL *string) (*models.Signature, error) GetSignature(ctx context.Context, signatureID string) (*models.Signature, error) GetActivePullRequestMetadata(ctx context.Context, gitHubAuthorUsername, gitHubAuthorEmail string) (*ActivePullRequest, error) @@ -447,37 +447,65 @@ func (repo repository) GetSignature(ctx context.Context, signatureID string) (*m } // CreateOrUpdateSignature either creates or updates the signature record -func (repo repository) CreateOrUpdateSignature(ctx context.Context, signature *models.Signature) (*models.Signature, error) { +func (repo repository) UpdateEnvelopeDetails(ctx context.Context, signatureID, envelopeID string, signURL *string) (*models.Signature, error) { f := logrus.Fields{ - "functionName": "v1.signatures.repository.CreateOrUpdateSignature", + "functionName": "v1.signatures.repository.UpdateEnvelopeDetails", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + "signatureID": signatureID, + "envelopeID": envelopeID, } - // Check if we have an existing signature record - existingSignature, sigErr := repo.GetSignature(ctx, signature.SignatureID) - if sigErr != nil { - log.WithFields(f).Warnf("error retrieving signature by ID: %s, error: %v", signature.SignatureID, sigErr) - return nil, sigErr + log.WithFields(f).Debugf("setting envelope details....") + + updateExpression := "SET signature_envelope_id = :envelopeId " + expressionAttributeValues := map[string]*dynamodb.AttributeValue{ + ":envelopeId": { + S: aws.String(envelopeID), + }, } - // If we have an existing signature record, we need to update it - if existingSignature != nil { - log.WithFields(f).Debugf("updating existing signature record for signature ID: %s", signature.SignatureID) - return repo.updateSignature(ctx, signature) + if signURL != nil { + updateExpression += ",signature_sign_url = :signUrl " + expressionAttributeValues[":signUrl"] = &dynamodb.AttributeValue{ + S: aws.String(*signURL), + } } - return nil, nil -} + // Create the update input + input := &dynamodb.UpdateItemInput{ + TableName: aws.String(repo.signatureTableName), + Key: map[string]*dynamodb.AttributeValue{ + "signature_id": { + S: aws.String(signatureID), + }, + }, + UpdateExpression: aws.String(updateExpression), + ExpressionAttributeValues: expressionAttributeValues, + ReturnValues: aws.String("ALL_NEW"), + } -// updateSignature updates the specified signature record -func (repo repository) updateSignature(ctx context.Context, signature *models.Signature) (*models.Signature, error) { - // f := logrus.Fields{ - // "functionName": "v1.signatures.repository.updateSignature", - // utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - // } + // Update the record in the DynamoDB table + result, err := repo.dynamoDBClient.UpdateItem(input) + if err != nil { + log.WithFields(f).Errorf("Error updating signature record: %v", err) + return nil, err + } - // // Update the record in the database - return nil, nil + // Update the record in the DynamoDB table + var updatedItem ItemSignature + + if err := dynamodbattribute.UnmarshalMap(result.Attributes, &updatedItem); err != nil { + log.WithFields(f).Errorf("Error unmarshalling updated item: %v", err) + return nil, err + } + + log.WithFields(f).Debugf("updated signature record for: %s", signatureID) + return &models.Signature{ + SignatureID: updatedItem.SignatureID, + SignatureSignURL: updatedItem.SignatureSignURL, + ProjectID: updatedItem.SignatureProjectID, + SignatureReferenceID: updatedItem.SignatureReferenceID, + }, nil } // GetIndividualSignature returns the signature record for the specified CLA Group and User diff --git a/cla-backend-go/signatures/service.go b/cla-backend-go/signatures/service.go index f2fa66d3f..c7abbc386 100644 --- a/cla-backend-go/signatures/service.go +++ b/cla-backend-go/signatures/service.go @@ -69,7 +69,7 @@ type SignatureService interface { createOrGetEmployeeModels(ctx context.Context, claGroupModel *models.ClaGroup, companyModel *models.Company, corporateSignatureModel *models.Signature) ([]*models.User, error) CreateOrUpdateEmployeeSignature(ctx context.Context, claGroupModel *models.ClaGroup, companyModel *models.Company, corporateSignatureModel *models.Signature) ([]*models.User, error) - CreateOrUpdateSignature(ctx context.Context, signature *models.Signature) (*models.Signature, error) + UpdateEnvelopeDetails(ctx context.Context, signatureID, envelopeID string, signURL *string) (*models.Signature, error) handleGitHubStatusUpdate(ctx context.Context, employeeUserModel *models.User) error } @@ -143,8 +143,8 @@ func (s service) GetProjectSignatures(ctx context.Context, params signatures.Get } // CreateOrUpdateEmployeeSignature creates or updates the specified signature -func (s service) CreateOrUpdateSignature(ctx context.Context, signature *models.Signature) (*models.Signature, error) { - return s.repo.CreateOrUpdateSignature(ctx, signature) +func (s service) UpdateEnvelopeDetails(ctx context.Context, signatureID, envelopeID string, signURL *string) (*models.Signature, error) { + return s.repo.UpdateEnvelopeDetails(ctx, signatureID, envelopeID, signURL) } // CreateProjectSummaryReport generates a project summary report based on the specified input diff --git a/cla-backend-go/swagger/common/signature.yaml b/cla-backend-go/swagger/common/signature.yaml index 795eeb1ef..707d36f97 100644 --- a/cla-backend-go/swagger/common/signature.yaml +++ b/cla-backend-go/swagger/common/signature.yaml @@ -125,6 +125,8 @@ properties: signatureSignURL: type: string description: the signature Document Sign URL + sigTypeSignedApprovedId: + type: string signatureCallbackURL: type: string description: the signature callback URL diff --git a/cla-backend-go/v2/sign/docusign.go b/cla-backend-go/v2/sign/docusign.go index 3e743b193..c5b13c6b3 100644 --- a/cla-backend-go/v2/sign/docusign.go +++ b/cla-backend-go/v2/sign/docusign.go @@ -4,11 +4,13 @@ package sign import ( + "bytes" "context" "encoding/json" "errors" "fmt" "io" + "mime/multipart" "net/http" "strings" @@ -90,6 +92,64 @@ func (s *service) getAccessToken(ctx context.Context) (string, error) { } +func getAccountID(accessToken string) (string, error) { + f := logrus.Fields{ + "functionName": "v2.getAccountID", + } + + // Create the request + url := fmt.Sprintf("https://%s/oauth/userinfo", utils.GetProperty("DOCUSIGN_AUTH_SERVER")) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem creating the HTTP request") + return "", err + } + + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken)) + req.Header.Add("Accept", "application/json") + + // Make the request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem making the HTTP request") + return "", err + } + + defer func() { + if err = resp.Body.Close(); err != nil { + log.WithFields(f).WithError(err).Warnf("problem closing the response body") + } + }() + + // Parse the response + responsePayload, err := io.ReadAll(resp.Body) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem reading the response body") + return "", err + } + + if resp.StatusCode != http.StatusOK { + log.WithFields(f).Warnf("problem making the HTTP request - status code: %d", resp.StatusCode) + return "", errors.New("problem making the HTTP request") + } + + var accountResponse DocuSignUserInfoResponse + err = json.Unmarshal(responsePayload, &accountResponse) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem unmarshalling the response body") + return "", err + } + + accountID := accountResponse.Accounts[0].AccountId + + log.WithFields(f).Debugf("account ID: %s", accountID) + + return accountID, nil +} + // Void envelope func (s *service) VoidEnvelope(ctx context.Context, envelopeID, message string) error { f := logrus.Fields{ @@ -118,7 +178,16 @@ func (s *service) VoidEnvelope(ctx context.Context, envelopeID, message string) return err } - url := fmt.Sprintf("https://%s/restapi/v2.1/accounts/%s/envelopes/%s/void", utils.GetProperty("DOCUSIGN_ROOT_URL"), utils.GetProperty("DOCUSIGN_ACCOUNT_ID"), envelopeID) + accountID, err := getAccountID(accessToken) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem getting the account ID") + return err + } + + log.WithFields(f).Debugf("docusign account ID: %s", accountID) + + url := fmt.Sprintf("%s/accounts/%s/envelopes/%s/void", utils.GetProperty("DOCUSIGN_ROOT_URL"), accountID, envelopeID) req, err := http.NewRequest("PUT", url, strings.NewReader(string(voidRequestJSON))) @@ -158,8 +227,284 @@ func (s *service) VoidEnvelope(ctx context.Context, envelopeID, message string) } +func (s *service) createEnvelope(ctx context.Context, payload *DocuSignEnvelopeRequest) (string, error) { + f := logrus.Fields{ + "functionName": "v2.createEnvelope", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + + // Serialize the signRequest into JSON + requestJSON, err := json.Marshal(payload) + if err != nil { + return "", err + } + + log.WithFields(f).Debugf("sign request: %+v", string(requestJSON)) + + // Get the access token + accessToken, err := s.getAccessToken(ctx) + + if err != nil { + return "", err + } + + // Get Account ID + + accountID, err := getAccountID(accessToken) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem getting the account ID") + return "", err + } + + log.WithFields(f).Debugf("docusign account ID: %s", accountID) + + // Create the request + url := fmt.Sprintf("%s/accounts/%s/envelopes", utils.GetProperty("DOCUSIGN_ROOT_URL"), accountID) + + req, err := http.NewRequest("POST", url, strings.NewReader(string(requestJSON))) + + if err != nil { + return "", err + } + + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken)) + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + + // Make the request + client := &http.Client{} + + resp, err := client.Do(req) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem making the HTTP request") + return "", err + } + + defer func() { + if err = resp.Body.Close(); err != nil { + log.WithFields(f).WithError(err).Warnf("problem closing the response body") + } + }() + + responsePayload, err := io.ReadAll(resp.Body) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem reading the response body") + return "", err + } + + if resp.StatusCode != http.StatusCreated { + log.WithFields(f).Warnf("problem making the HTTP request - status code: %d - response : %s", resp.StatusCode, string(responsePayload)) + return "", errors.New("problem making the HTTP request") + } + + var response DocuSignEnvelopeResponse + + err = json.Unmarshal(responsePayload, &response) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem unmarshalling the response body") + return "", err + } + + return response.EnvelopeId, nil + +} + +func (s *service) addDocumentToEnvelope(ctx context.Context, envelopeID, documentName string, document []byte) error { + f := logrus.Fields{ + "functionName": "v2.addDocumentToEnvelope", + } + + const method = "PUT" + + // Get the access token + accessToken, err := s.getAccessToken(ctx) + + if err != nil { + return err + } + + // Get Account ID + + accountID, err := getAccountID(accessToken) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem getting the account ID") + return err + } + + log.WithFields(f).Debugf("docusign account ID: %s", accountID) + + url := fmt.Sprintf("%s/accounts/%s/envelopes/%s/documents/1", utils.GetProperty("DOCUSIGN_ROOT_URL"), accountID, envelopeID) + + log.WithFields(f).Debugf("url: %s", url) + + body := &bytes.Buffer{} + + writer := multipart.NewWriter(body) + + part, partErr := writer.CreateFormFile("file", documentName) + if partErr != nil { + return partErr + } + + _, copyErr := io.Copy(part, bytes.NewReader(document)) + + if copyErr != nil { + return copyErr + } + + closeErr := writer.Close() + if closeErr != nil { + return closeErr + } + + // create the http request + req, err := http.NewRequest(method, url, body) + if err != nil { + return err + } + + // Set headers + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) + req.Header.Set("Content-Disposition", fmt.Sprintf("filename=\"%s\"", documentName)) + req.Header.Set("Content-Type", "application/pdf") + req.Header.Set("Accept", "application/json") + + log.WithFields(f).Debugf("adding document to envelope with url: %s %s", method, url) + + // Send HTTP request + client := &http.Client{} + resp, clientErr := client.Do(req) + if clientErr != nil { + log.WithFields(f).WithError(clientErr).Warnf("problem invoking envelope document upload request to %s %s", method, url) + return clientErr + } + + //log.WithFields(f).Debugf("response: %+v", resp) + responsePayload, readErr := io.ReadAll(resp.Body) + if readErr != nil { + log.WithFields(f).WithError(readErr).Warnf("problem reading response body %+v", resp.Body) + return readErr + } + + // Expecting a 200 response + if resp.StatusCode != 200 { + msg := fmt.Sprintf("problem invoking http %s request to %s - response status code is not 200: %d - response is: %+v", method, url, resp.StatusCode, string(responsePayload)) + log.WithFields(f).Warn(msg) + return errors.New(msg) + } + + defer func() { + closeErr = resp.Body.Close() + if closeErr != nil { + log.WithFields(f).WithError(closeErr).Warnf("problem closing response body") + } + }() + + var documentUpdateResponseModel DocuSignUpdateDocumentResponse + unmarshalErr := json.Unmarshal(responsePayload, &documentUpdateResponseModel) + if unmarshalErr != nil { + log.WithFields(f).WithError(unmarshalErr).Warnf("problem unmarshalling document update to the envelope response model JSON data") + return unmarshalErr + } + + log.WithFields(f).Debugf("successfully added document to envelope response body, uri: %s, documentGuid: %s, response: %+v", documentUpdateResponseModel.Uri, documentUpdateResponseModel.DocumentIdGuid, documentUpdateResponseModel) + + return nil + +} + +func (s *service) getEnvelopeRecipients(ctx context.Context, envelopeID string) ([]Signer, error) { + f := logrus.Fields{ + "functionName": "v2.getEnvelopeRecipients", + "envelopeID": envelopeID, + } + + // Get the access token + accessToken, err := s.getAccessToken(ctx) + + if err != nil { + return nil, err + } + + log.WithFields(f).Debugf("access token: %s", accessToken) + + // Get Account ID + + accountID, err := getAccountID(accessToken) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem getting the account ID") + return nil, err + } + + log.WithFields(f).Debugf("docusign account ID: %s", accountID) + + // Create the request + url := fmt.Sprintf("%s/accounts/%s/envelopes/%s/recipients", utils.GetProperty("DOCUSIGN_ROOT_URL"), accountID, envelopeID) + + req, err := http.NewRequest("GET", url, nil) + + if err != nil { + log.WithFields(f).Debugf("%+v", err) + return nil, err + } + + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken)) + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + + // Make the request + client := &http.Client{} + + resp, err := client.Do(req) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem making the HTTP request") + return nil, err + } + + defer func() { + if err = resp.Body.Close(); err != nil { + log.WithFields(f).WithError(err).Warnf("problem closing the response body") + } + }() + + responsePayload, err := io.ReadAll(resp.Body) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem reading the response body") + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, errors.New("problem getting getting recipients ") + } + + var response *DocusignRecipientResponse + + err = json.Unmarshal(responsePayload, &response) + + if err != nil { + log.WithFields(f).Debugf("unable to unmarshall response: %+v", err) + return nil, err + } + + log.WithFields(f).Debugf("got %d recipients", len(response.Signers)) + + return response.Signers, nil +} + // Function to create a DocuSign envelope -func (s *service) PrepareSignRequest(ctx context.Context, signRequest *DocuSignEnvelopeRequest) (*DocuSignEnvelopeResponse, error) { +func (s *service) PrepareSignRequest(ctx context.Context, signRequest *DocuSignEnvelopeRequest) (*DocusignEnvelopeResponse, error) { + f := logrus.Fields{ + "functionName": "v2.PrepareSignRequest", + utils.XREQUESTID: ctx.Value(utils.XREQUESTID), + } + // Serialize the signRequest into JSON requestJSON, err := json.Marshal(signRequest) if err != nil { @@ -173,9 +518,21 @@ func (s *service) PrepareSignRequest(ctx context.Context, signRequest *DocuSignE return nil, err } - // Create the request + log.WithFields(f).Debugf("access token: %s", accessToken) + + // Get Account ID + + accountID, err := getAccountID(accessToken) - url := fmt.Sprintf("https://%s/restapi/v2.1/accounts/%s/envelopes", utils.GetProperty("DOCUSIGN_ROOT_URL"), utils.GetProperty("DOCUSIGN_ACCOUNT_ID")) + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem getting the account ID") + return nil, err + } + + log.WithFields(f).Debugf("docusign account ID: %s", accountID) + + // Create the request + url := fmt.Sprintf("%s/accounts/%s/envelopes", utils.GetProperty("DOCUSIGN_ROOT_URL"), accountID) req, err := http.NewRequest("POST", url, strings.NewReader(string(requestJSON))) @@ -185,51 +542,59 @@ func (s *service) PrepareSignRequest(ctx context.Context, signRequest *DocuSignE req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken)) req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") // Make the request client := &http.Client{} resp, err := client.Do(req) - if err != nil { + log.WithFields(f).WithError(err).Warnf("problem making the HTTP request") return nil, err } defer func() { if err = resp.Body.Close(); err != nil { - log.Warnf("problem closing the response body") + log.WithFields(f).WithError(err).Warnf("problem closing the response body") } }() - // Parse the response responsePayload, err := io.ReadAll(resp.Body) if err != nil { + log.WithFields(f).WithError(err).Warnf("problem reading the response body") return nil, err } - if resp.StatusCode != http.StatusOK { + if resp.StatusCode != http.StatusCreated { + log.WithFields(f).Warnf("problem making the HTTP request - status code: %d - response : %s", resp.StatusCode, string(responsePayload)) return nil, errors.New("problem making the HTTP request") } - var envelopeResponse DocuSignEnvelopeResponse + var response DocusignEnvelopeResponse - err = json.Unmarshal(responsePayload, &envelopeResponse) + err = json.Unmarshal(responsePayload, &response) if err != nil { + log.WithFields(f).WithError(err).Warnf("problem unmarshalling the response body") return nil, err } - return &envelopeResponse, nil + return &response, nil + +} + +// Define a struct to represent the response from the DocuSign API. +type RecipientViewResponse struct { + URL string `json:"url"` } // GetSignURL fetches the signing URL for the specified envelope and recipient -func (s *service) GetSignURL(envelopeID, recipientID, returnURL string) (string, error) { +func (s *service) GetSignURL(email, recipientID, userName, clientUserId, envelopeID, returnURL string) (string, error) { f := logrus.Fields{ "functionName": "v2.GetSignURL", - "envelopeID": envelopeID, "recipientID": recipientID, "returnURL": returnURL, } @@ -241,44 +606,54 @@ func (s *service) GetSignURL(envelopeID, recipientID, returnURL string) (string, return "", err } + // Get Account ID + accountID, err := getAccountID(accessToken) + + if err != nil { + log.WithFields(f).WithError(err).Warnf("problem getting the account ID") + return "", err + } + // Create the request - url := fmt.Sprintf("https://%s/restapi/v2.1/accounts/%s/envelopes/%s/views/recipient", utils.GetProperty("DOCUSIGN_ROOT_URL"), utils.GetProperty("DOCUSIGN_ACCOUNT_ID"), envelopeID) + url := fmt.Sprintf("%s/accounts/%s/envelopes/%s/views/recipient", utils.GetProperty("DOCUSIGN_ROOT_URL"), accountID, envelopeID) - req, err := http.NewRequest("POST", url, nil) + viewRecipientRequest := DocusignRecipientView{ + Email: email, + Username: userName, + RecipientID: recipientID, + ReturnURL: returnURL, + AuthenticaionMethod: "None", + } - if err != nil { - return "", err + if clientUserId != "" { + viewRecipientRequest.ClientUserId = clientUserId } - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken)) - req.Header.Add("Content-Type", "application/json") + jsonRequest, err := json.Marshal(viewRecipientRequest) - // Create the request body - requestBody := struct { - ReturnURL string `json:"returnUrl"` - ClientUserID string `json:"clientUserId"` - RecipientID string `json:"recipientId"` - }{ - ReturnURL: returnURL, - ClientUserID: recipientID, - RecipientID: recipientID, + if err != nil { + log.WithFields(f).Debugf("unable to marshal http request") + return "", err } - requestBodyJSON, err := json.Marshal(requestBody) + log.WithFields(f).Debugf("payload: %s", string(jsonRequest)) + + req, err := http.NewRequest("POST", url, strings.NewReader(string(jsonRequest))) if err != nil { return "", err } - req.Body = io.NopCloser(strings.NewReader(string(requestBodyJSON))) + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken)) + req.Header.Add("Content-Type", "application/json") - // Make the request client := &http.Client{} resp, err := client.Do(req) if err != nil { + log.WithFields(f).Debugf("%+v", err) return "", err } @@ -288,24 +663,24 @@ func (s *service) GetSignURL(envelopeID, recipientID, returnURL string) (string, } }() - // Parse the response - - // Parse the response JSON - var response struct { - Url string `json:"url"` - } - - responsePayload, err := io.ReadAll(resp.Body) - + body, err := io.ReadAll(resp.Body) if err != nil { + log.WithFields(f).Debugf("%+v", err) return "", err } - err = json.Unmarshal(responsePayload, &response) + if resp.StatusCode != http.StatusCreated { + log.WithFields(f).Debugf("response: %+s and status code: %d", string(body), resp.StatusCode) + return "", errors.New("failed to get signing URL") + } - if err != nil { + var viewResponse RecipientViewResponse + if err := json.Unmarshal(body, &viewResponse); err != nil { + log.WithFields(f).Debug("failed to unmarshall response") return "", err } - return response.Url, nil + log.WithFields(f).Debugf("View response: %+v", viewResponse) + + return viewResponse.URL, nil } diff --git a/cla-backend-go/v2/sign/handlers.go b/cla-backend-go/v2/sign/handlers.go index fd6fd1119..453c15969 100644 --- a/cla-backend-go/v2/sign/handlers.go +++ b/cla-backend-go/v2/sign/handlers.go @@ -5,16 +5,14 @@ package sign import ( "context" - "encoding/json" "errors" "fmt" - "net/http" "strings" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/projects_cla_groups" + "github.com/communitybridge/easycla/cla-backend-go/users" "github.com/sirupsen/logrus" - "golang.org/x/oauth2" "github.com/LF-Engineering/lfx-kit/auth" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" @@ -26,7 +24,7 @@ import ( ) // Configure API call -func Configure(api *operations.EasyclaAPI, service Service) { +func Configure(api *operations.EasyclaAPI, service Service, userService users.Service) { // Retrieve a list of available templates api.SignRequestCorporateSignatureHandler = sign.RequestCorporateSignatureHandlerFunc( func(params sign.RequestCorporateSignatureParams, user *auth.User) middleware.Responder { @@ -93,48 +91,20 @@ func Configure(api *operations.EasyclaAPI, service Service) { } var resp *models.IndividualSignatureOutput var err error - var preferredEmail string = "" - - session := getRequestSession(params.HTTPRequest) - if session == nil { - msg := "session not found" - log.WithFields(f).Warn(msg) - return sign.NewRequestIndividualSignatureBadRequest().WithPayload(errorResponse(reqId, errors.New(msg))) - } - - clientID := utils.GetProperty("GH_OAUTH_CLIENT_ID") - if clientID == "" { - msg := "client id not found" - log.WithFields(f).Warn(msg) - return sign.NewRequestIndividualSignatureBadRequest().WithPayload(errorResponse(reqId, errors.New(msg))) - } + var preferredEmail string if strings.ToLower(params.Input.ReturnURLType) == Github || strings.ToLower(params.Input.ReturnURLType) == Gitlab { - if strings.ToLower(params.Input.ReturnURLType) == Github { - log.WithFields(f).Debug("fetching github emails") - emails, fetchErr := fetchGithubEmails(session, clientID) - if fetchErr != nil { - return sign.NewRequestIndividualSignatureBadRequest().WithPayload(errorResponse(reqId, err)) - } - - if len(emails) == 0 { - msg := "no emails found" - log.WithFields(f).Warn(msg) - return sign.NewRequestIndividualSignatureBadRequest().WithPayload(errorResponse(reqId, errors.New(msg))) - } - for _, email := range emails { - if email["verified"].(bool) && email["primary"].(bool) { - if emailVal, ok := email["email"].(string); ok { - preferredEmail = emailVal - } - break - } - } - } else { - log.WithFields(f).Debug("fetching gitlab emails") - preferredEmail = "" //TODO: fetch gitlab emails for gitlab + log.WithFields(f).Debug("fetching user emails") + user, userErr := userService.GetUser(*params.Input.UserID) + if userErr != nil { + return sign.NewRequestIndividualSignatureBadRequest().WithPayload(errorResponse(reqId, userErr)) } - + if len(user.Emails) == 0 { + msg := "no emails found" + log.WithFields(f).Warn(msg) + return sign.NewRequestIndividualSignatureBadRequest().WithPayload(errorResponse(reqId, errors.New(msg))) + } + preferredEmail = user.Emails[0] log.WithFields(f).Debug("requesting individual signature for github/gitlab") resp, err = service.RequestIndividualSignature(ctx, params.Input, preferredEmail) } else if strings.ToLower(params.Input.ReturnURLType) == "gerrit" { @@ -171,58 +141,3 @@ func errorResponse(reqID string, err error) *models.ErrorResponse { return &e } - -func getRequestSession(req *http.Request) map[string]interface{} { - session := req.Context().Value("session") - if session == nil { - return nil - } - return session.(map[string]interface{}) -} - -func fetchGithubEmails(session map[string]interface{}, clientID string) ([]map[string]interface{}, error) { - var emails []map[string]interface{} - var token string - - if tokenVal, ok := session["token"].(string); ok { - token = tokenVal - } else { - return emails, nil - } - - if token == "" { - return emails, nil - } - - oauth2Config := oauth2.Config{ - ClientID: clientID, - } - - oauth2Token := &oauth2.Token{ - AccessToken: token, - } - - client := oauth2Config.Client(context.Background(), oauth2Token) - - resp, err := client.Get("https://api.github.com/user/emails") - if err != nil { - return emails, err - } - - defer func() { - if err = resp.Body.Close(); err != nil { - log.Warnf("problem closing the response body") - } - }() - - if resp.StatusCode != 200 { - return emails, err - } - - err = json.NewDecoder(resp.Body).Decode(&emails) - if err != nil { - return emails, err - } - - return emails, err -} diff --git a/cla-backend-go/v2/sign/models.go b/cla-backend-go/v2/sign/models.go index 0d27aed26..2b3a16b75 100644 --- a/cla-backend-go/v2/sign/models.go +++ b/cla-backend-go/v2/sign/models.go @@ -43,7 +43,7 @@ type DocuSignEnvelopeRequest struct { EnvelopeId string `json:"envelopeId,omitempty"` // The envelope ID of the envelope EnvelopeIdStamping string `json:"envelopeIdStamping,omitempty"` // When true, Envelope ID Stamping is enabled. After a document or attachment is stamped with an Envelope ID, the ID is seen by all recipients and becomes a permanent part of the document and cannot be removed. TemplateId string `json:"templateId,omitempty"` // The ID of the template. If a value is not provided, DocuSign generates a value. - Documents []DocuSignDocument `json:"document,omitempty"` // A data model containing details about the documents associated with the envelope + Documents []DocuSignDocument `json:"documents,omitempty"` // A data model containing details about the documents associated with the envelope DocumentBase64 string `json:"documentBase64,omitempty"` // The document's bytes. This field can be used to include a base64 version of the document bytes within an envelope definition instead of sending the document using a multi-part HTTP request. The maximum document size is smaller if this field is used due to the overhead of the base64 encoding. DocumentsCombinedUri string `json:"documentsCombinedUri,omitempty"` // The URI for retrieving all of the documents associated with the envelope as a single PDF file. DocumentsUri string `json:"documentsUri,omitempty"` // The URI for retrieving all of the documents associated with the envelope as separate files. @@ -71,6 +71,14 @@ type DocuSignEnvelopeRequest struct { Status string `json:"status,omitempty"` } +// DocusignEnvelopeResponse +type DocusignEnvelopeResponse struct { + EnvelopeId string `json:"envelopeId,omitempty"` + Status string `json:"status,omitempty"` + StatusDateTime string `json:"statusDateTime,omitempty"` + Uri string `json:"uri,omitempty"` +} + // DocuSignDocument is the data model for a document from DocuSign type DocuSignDocument struct { DocumentId string `json:"documentId,omitempty"` // Specifies the document ID of this document. This value is used by tabs to determine which document they appear in. @@ -318,13 +326,13 @@ type ClaSignatoryEmailParams struct { } type DocuSignRecipientEvent struct { - RecipientEventStatusCode string `json:"recipientEventStatusCode"` + EnvelopeEventStatusCode string `json:"envelopeEventStatusCode"` } type DocuSignEventNotification struct { - URL string - LoggingEnabled bool - RecipientEvents []DocuSignRecipientEvent + URL string `json:"url"` + LoggingEnabled bool `json:"loggingEnabled"` + EnvelopeEvents []DocuSignRecipientEvent `json:"envelopeEvents"` } type Recipient struct { @@ -332,3 +340,51 @@ type Recipient struct { Email string `json:"email"` // Other recipient-specific fields } + +// DocuSignUpdateDocumentResponse is the response body for adding/updating a document to an envelope from DocuSign +type DocuSignUpdateDocumentResponse struct { + /* + {"documentId":"1","documentIdGuid":"2c205f31-4c6b-4237-b6bc-d79457b949a5","name":"document.pdf","type":"content","uri":"/envelopes/ebeee6a6-c17f-4d05-8441-38d5c1ad9675/documents/1","order":"1","containsPdfFormFields":"false","templateRequired":"false","authoritativeCopy":"false"} + */ + DocumentId string `json:"documentId,omitempty"` + DocumentIdGuid string `json:"documentIdGuid,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Uri string `json:"uri,omitempty"` + Order string `json:"order,omitempty"` + ContainsPdfFormFields string `json:"containsPdfFormFields,omitempty"` + TemplateRequired string `json:"templateRequired,omitempty"` + AuthoritativeCopy string `json:"authoritativeCopy,omitempty"` +} + +type Signer struct { + CreationReason string `json:"creationReason"` + IsBulkRecipient string `json:"isBulkRecipient"` + Name string `json:"name"` + Email string `json:"email"` + RecipientId string `json:"recipientId"` + RecipientIdGuid string `json:"recipientIdGuid"` + RequireIdLookup string `json:"requireIdLookup"` + UserId string `json:"userId"` + ClientUserId string `json:"clientUserId"` + RoutingOrder string `json:"routingOrder"` + RoleName string `json:"roleName"` + Status string `json:"status"` +} + +type DocusignRecipientResponse struct { + Signers []Signer `json:"signers"` +} + +type DocusignRecipientView struct { + Email string `json:"email"` + Username string `json:"userName"` + ReturnURL string `json:"returnUrl"` + RecipientID string `json:"recipientId"` + ClientUserId string `json:"clientUserId,omitempty"` + AuthenticaionMethod string `json:"authenticationMethod"` +} + +type DocusignRecipientViewResponse struct { + URL string `json:"url"` +} diff --git a/cla-backend-go/v2/sign/service.go b/cla-backend-go/v2/sign/service.go index 026011f48..8cf047464 100644 --- a/cla-backend-go/v2/sign/service.go +++ b/cla-backend-go/v2/sign/service.go @@ -66,8 +66,10 @@ type ProjectRepo interface { // Service interface defines the sign service methods type Service interface { VoidEnvelope(ctx context.Context, envelopeID, message string) error - PrepareSignRequest(ctx context.Context, signRequest *DocuSignEnvelopeRequest) (*DocuSignEnvelopeResponse, error) - GetSignURL(envelopeID, recipientID, returnURL string) (string, error) + PrepareSignRequest(ctx context.Context, signRequest *DocuSignEnvelopeRequest) (*DocusignEnvelopeResponse, error) + GetSignURL(email, recipientID, userName, clientUserId, envelopeID, returnURL string) (string, error) + createEnvelope(ctx context.Context, payload *DocuSignEnvelopeRequest) (string, error) + addDocumentToEnvelope(ctx context.Context, envelopeID, documentName string, document []byte) error RequestCorporateSignature(ctx context.Context, lfUsername string, authorizationHeader string, input *models.CorporateSignatureInput) (*models.CorporateSignatureOutput, error) RequestIndividualSignature(ctx context.Context, input *models.IndividualSignatureInput, preferredEmail string) (*models.IndividualSignatureOutput, error) @@ -338,10 +340,10 @@ func (s *service) RequestIndividualSignature(ctx context.Context, input *models. f := logrus.Fields{ "functionName": "sign.RequestIndividualSignature", utils.XREQUESTID: ctx.Value(utils.XREQUESTID), - "projectID": input.ProjectID, + "projectID": *input.ProjectID, "returnURL": input.ReturnURL, "returnURLType": input.ReturnURLType, - "userID": input.UserID, + "userID": *input.UserID, } /** @@ -440,14 +442,15 @@ func (s *service) RequestIndividualSignature(ctx context.Context, input *models. // Regenerate and set the signing URL - This will update the signature record log.WithFields(f).Debugf("regenerating signing URL for user: %s", *input.UserID) - err = s.populateSignURL(ctx, latestSignature, callBackURL, "", "", false, "", "", defaultValues, preferredEmail) + var signURL string + signURL, err = s.populateSignURL(ctx, latestSignature, callBackURL, "", "", false, "", "", defaultValues, preferredEmail) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to populate sign url for user: %s", *input.UserID) return nil, err } return &models.IndividualSignatureOutput{ - SignURL: latestSignature.SignatureSignURL, + SignURL: signURL, SignatureID: latestSignature.SignatureID, UserID: latestSignature.SignatureReferenceID, ProjectID: *input.ProjectID, @@ -517,23 +520,20 @@ func (s *service) RequestIndividualSignature(ctx context.Context, input *models. // 10. Populate sign url log.WithFields(f).Debugf("populating sign url...") - err = s.populateSignURL(ctx, signatureModel, callBackURL, "", "", false, "", "", defaultValues, preferredEmail) + var signURL string + signURL, err = s.populateSignURL(ctx, signatureModel, callBackURL, "", "", false, "", "", defaultValues, preferredEmail) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to populate sign url for user: %s", *input.UserID) return nil, err } - // 11. Save signature - signature, err := s.signatureService.CreateOrUpdateSignature(ctx, signatureModel) - if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to create signature for user: %s", *input.UserID) - return nil, err - } + log.WithFields(f).Debugf("Updated signature: %+v", signatureModel) return &models.IndividualSignatureOutput{ - UserID: signature.SignatureReferenceID, - ProjectID: signature.ProjectID, - SignatureID: signature.SignatureID, + UserID: signatureModel.SignatureReferenceID, + ProjectID: signatureModel.ProjectID, + SignatureID: signatureModel.SignatureID, + SignURL: signURL, }, nil } @@ -642,15 +642,15 @@ func (s *service) getIndividualSignatureCallbackURL(ctx context.Context, userID } return fmt.Sprintf("%s/v2/signed/individual/%d/%s/%d", s.ClaV1ApiURL, installationId, repositoryID, pullRequestID), nil - } +//nolint:gocyclo func (s *service) populateSignURL(ctx context.Context, latestSignature *v1Models.Signature, callbackURL string, authorityOrSignatoryName, authorityOrSignatoryEmail string, sendAsEmail bool, claManagerName, claManagerEmail string, - defaultValues map[string]interface{}, preferredEmail string) error { + defaultValues map[string]interface{}, preferredEmail string) (string, error) { f := logrus.Fields{ "functionName": "sign.populateSignURL", @@ -680,7 +680,7 @@ func (s *service) populateSignURL(ctx context.Context, userDetails, err := s.populateUserDetails(ctx, signatureReferenceType, latestSignature, claManagerName, claManagerEmail, sendAsEmail, preferredEmail) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to populate user details for signatureReferenceType: %s", signatureReferenceType) - return err + return "", err } userSignatureName = userDetails.userSignatureName @@ -691,12 +691,12 @@ func (s *service) populateSignURL(ctx context.Context, project, err = s.projectRepo.GetCLAGroupByID(ctx, latestSignature.ProjectID, DontLoadRepoDetails) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to lookup project by ID: %s", latestSignature.ProjectID) - return err + return "", err } if project == nil { log.WithFields(f).WithError(err).Warnf("unable to lookup project by ID: %s", latestSignature.ProjectID) - return errors.New("no project lookup error") + return "", errors.New("no project lookup error") } if signatureReferenceType == utils.SignatureReferenceTypeCompany { @@ -704,14 +704,14 @@ func (s *service) populateSignURL(ctx context.Context, document, err = common.GetCurrentDocument(ctx, project.ProjectCorporateDocuments) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to lookup project corporate document for project: %s", latestSignature.ProjectID) - return err + return "", err } } else { log.WithFields(f).Debugf("loading project individual document...") document, err = common.GetCurrentDocument(ctx, project.ProjectIndividualDocuments) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to lookup project individual document for project: %s", latestSignature.ProjectID) - return err + return "", err } } @@ -726,14 +726,8 @@ func (s *service) populateSignURL(ctx context.Context, } } - // # Not sure what should be put in as documentId. - // document_id = uuid.uuid4().int & (1 << 16) - 1 # Random 16bit integer -.pylint: disable=no-member - - randomUuid := uuid.Must(uuid.NewV4()).String() - - documentID := int(randomUuid[0])<<8 + int(randomUuid[1]) - log.WithFields(f).Debugf("documentID: %d", documentID) - tab := getTabsFromDocument(&document, strconv.Itoa(documentID), defaultValues) + documentID := "1" + tab := getTabsFromDocument(&document, documentID, defaultValues) // # Create the envelope request object @@ -756,12 +750,12 @@ func (s *service) populateSignURL(ctx context.Context, pcgs, pcgErr := s.projectClaGroupsRepo.GetProjectsIdsForClaGroup(ctx, project.ProjectID) if pcgErr != nil { log.WithFields(f).Debugf("problem fetching project cla groups by id :%s, err: %+v", project.ProjectID, pcgErr) - return pcgErr + return "", pcgErr } if len(pcgs) == 0 { log.WithFields(f).Debugf("no project cla groups found for project id :%s", project.ProjectID) - return errors.New("no project cla groups found for project id") + return "", errors.New("no project cla groups found for project id") } var projectNames []string @@ -843,11 +837,11 @@ func (s *service) populateSignURL(ctx context.Context, var pdf []byte if document.DocumentS3URL != "" { - log.WithFields(f).Debugf("getting document resource from s3...") + log.WithFields(f).Debugf("getting document resource from s3: %s...", document.DocumentS3URL) pdf, err = s.getDocumentResource(document.DocumentS3URL) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to get document resource from s3 for document: %s", document.DocumentS3URL) - return err + return "", err } } else if strings.HasPrefix(contentType, "url+") { log.WithFields(f).Debugf("getting document resource from url...") @@ -855,7 +849,7 @@ func (s *service) populateSignURL(ctx context.Context, pdf, err = s.getDocumentResource(pdfURL) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to get document resource from url: %s", pdfURL) - return err + return "", err } } else { log.WithFields(f).Debugf("getting document resource from content...") @@ -865,12 +859,14 @@ func (s *service) populateSignURL(ctx context.Context, documentName := document.DocumentName log.WithFields(f).Debugf("documentName: %s", documentName) - log.WithFields(f).Debugf("documentID: %d", documentID) log.WithFields(f).Debugf("contentType: %s", contentType) docusignDocument := DocuSignDocument{ Name: documentName, - DocumentId: strconv.Itoa(documentID), + DocumentId: "1", + FileExtension: "pdf", + FileFormatHint: "pdf", + Order: "1", DocumentBase64: base64.StdEncoding.EncodeToString(pdf), } @@ -882,14 +878,14 @@ func (s *service) populateSignURL(ctx context.Context, // all signers on a document finish signing the document. recipientEvents := []DocuSignRecipientEvent{ { - RecipientEventStatusCode: "Completed", + EnvelopeEventStatusCode: "Completed", }, } eventNotification := DocuSignEventNotification{ - URL: callbackURL, - LoggingEnabled: true, - RecipientEvents: recipientEvents, + URL: callbackURL, + LoggingEnabled: true, + EnvelopeEvents: recipientEvents, } envelopeRequest = DocuSignEnvelopeRequest{ @@ -908,6 +904,7 @@ func (s *service) populateSignURL(ctx context.Context, } } else { + envelopeRequest = DocuSignEnvelopeRequest{ Documents: []DocuSignDocument{ docusignDocument, @@ -924,48 +921,60 @@ func (s *service) populateSignURL(ctx context.Context, } - log.WithFields(f).Debugf("envelopeRequest: %+v", envelopeRequest) - envelopeResponse, err := s.PrepareSignRequest(ctx, &envelopeRequest) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to create envelope for user: %s", latestSignature.SignatureReferenceID) - return err + return "", err } + log.WithFields(f).Debugf("envelopeID: %s", envelopeResponse.EnvelopeId) + var signatureSignURL *string + if !sendAsEmail { // The URL the user will be redirected to after signing. // This route will be in charge of extracting the signature's return_url and redirecting. - recipient := envelopeResponse.Recipients[0] + recipients, recipientErr := s.getEnvelopeRecipients(ctx, envelopeResponse.EnvelopeId) + if recipientErr != nil { + log.WithFields(f).Debugf("unable to fetch recipients for envelope: %s", envelopeResponse.EnvelopeId) + return "", recipientErr + } + + if len(recipients) == 0 { + log.WithFields(f).Debugf("no envelope recipients found : %s", envelopeResponse.EnvelopeId) + return "", errors.New("no envelope recipients found") + } + recipient := recipients[0] returnURL := fmt.Sprintf("%s/v2/return-url/%s", s.ClaV1ApiURL, recipient.ClientUserId) log.WithFields(f).Debugf("generating signature sign_url, using return-url as: %s", returnURL) - signURL, signErr := s.GetSignURL(envelopeID, recipient.RecipientId, returnURL) + signURL, signErr := s.GetSignURL(signer.Email, signer.RecipientId, signer.Name, signer.ClientUserId, envelopeResponse.EnvelopeId, returnURL) if signErr != nil { log.WithFields(f).WithError(err).Warnf("unable to get sign url for user: %s", latestSignature.SignatureReferenceID) - return signErr + return "", signErr } log.WithFields(f).Debugf("setting signature sign_url as: %s", signURL) - latestSignature.SignatureSignURL = signURL + signatureSignURL = &signURL } // Save Envelope ID in signature. log.WithFields(f).Debugf("saving signature to database...") latestSignature.SignatureEnvelopeID = envelopeResponse.EnvelopeId - latestSignature, err = s.signatureService.CreateOrUpdateSignature(ctx, latestSignature) + log.WithFields(f).Debugf("signature: %+v", latestSignature) + + latestSignature, err = s.signatureService.UpdateEnvelopeDetails(ctx, latestSignature.SignatureID, envelopeResponse.EnvelopeId, signatureSignURL) if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to save signature to database for user: %s", latestSignature.SignatureReferenceID) - return err + log.WithFields(f).WithError(err).Warnf("unable to save signature to database for user: %s", latestSignature.SignatureID) + return "", err } - log.WithFields(f).Debugf("saved signature to database - id: %s", latestSignature.SignatureID) - log.WithFields(f).Debugf("populate_sign_url - complete") + log.WithFields(f).Debugf("populate_sign_url - complete: %s", *signatureSignURL) - return nil + return *signatureSignURL, nil } type UserSignDetails struct { @@ -1404,19 +1413,13 @@ func (s *service) requestCorporateSignature(ctx context.Context, apiURL string, // 7. Populate sign url log.WithFields(f).Debugf("populating sign url...") - err = s.populateSignURL(ctx, companySignature, callbackURL, input.AuthorityName, input.AuthorityEmail, input.SendAsEmail, claUser.Username, currentUserEmail, defaultValues, currentUserEmail) + _, err = s.populateSignURL(ctx, companySignature, callbackURL, input.AuthorityName, input.AuthorityEmail, input.SendAsEmail, claUser.Username, currentUserEmail, defaultValues, currentUserEmail) if err != nil { log.WithFields(f).WithError(err).Warnf("unable to populate sign url for company: %s", input.CompanyID) return nil, err } - // 8. Save signature - signature, err := s.signatureService.CreateOrUpdateSignature(ctx, companySignature) - if err != nil { - log.WithFields(f).WithError(err).Warnf("unable to create signature for company: %s", input.CompanyID) - return nil, err - } - return signature, nil + return companySignature, nil } func removeSignatoryRole(ctx context.Context, userEmail string, companySFID string, projectSFID string) error { diff --git a/cla-backend-go/v2/store/repository.go b/cla-backend-go/v2/store/repository.go index 55f06dcd0..fb18b0c73 100644 --- a/cla-backend-go/v2/store/repository.go +++ b/cla-backend-go/v2/store/repository.go @@ -20,9 +20,9 @@ import ( // DBStore represents DB Model for the store table type DBStore struct { - Key string `dynamodbav:"key"` - Value string `dynamodbav:"value"` - Expire int64 `dynamodbav:"expire"` + Key string `dynamodbav:"key"` + Value string `dynamodbav:"value"` + Expire float64 `dynamodbav:"expire"` } // Repository interface @@ -78,22 +78,37 @@ func (r repo) GetActiveSignatureMetaData(ctx context.Context, userId string) (ma return metadata, nil } - var store DBStore + // var store DBStore - err = dynamodbattribute.UnmarshalMap(result.Item, &store) + // err = dynamodbattribute.UnmarshalMap(result.Item, &store) + // if err != nil { + // log.WithFields(f).WithError(err).Warn("problem unmarshalling store record") + // return metadata, err + // } + + // log.WithFields(f).Debugf("Signature meta record data found: %+v ", store) + + // err = json.Unmarshal([]byte(store.Value), &metadata) + // if err != nil { + // log.WithFields(f).WithError(err).Warn("problem unmarshalling store record") + // return metadata, err + // } + + var jsonStr string + + err = dynamodbattribute.Unmarshal(result.Item["value"], &jsonStr) if err != nil { log.WithFields(f).WithError(err).Warn("problem unmarshalling store record") return metadata, err } - log.WithFields(f).Debugf("Signature meta record data found: %+v ", store) + log.WithFields(f).Debugf("Signature string record data found: %s ", jsonStr) + + err = json.Unmarshal([]byte(jsonStr), &metadata) - err = json.Unmarshal([]byte(store.Value), &metadata) if err != nil { - log.WithFields(f).WithError(err).Warn("problem unmarshalling store record") - return metadata, err + log.WithFields(f).WithError(err).Warn("problem unmarshalling json string for metadata") } - return metadata, nil } @@ -110,7 +125,7 @@ func (r repo) SetActiveSignatureMetaData(ctx context.Context, key string, expire store := DBStore{ Key: key, Value: value, - Expire: expire, + Expire: float64(expire), } log.WithFields(f).Debugf("key: %s ", store.Key)