Skip to content

Commit

Permalink
Bug/Docusign Integration
Browse files Browse the repository at this point in the history
- Created signature for icla and ccla signflows

Signed-off-by: Harold Wanyama <hwanyama@contractor.linuxfoundation.org>
  • Loading branch information
nickmango committed Nov 15, 2023
1 parent f3151a6 commit 0006a6c
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 41 deletions.
31 changes: 31 additions & 0 deletions cla-backend-go/signatures/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type SignatureRepository interface {
ValidateProjectRecord(ctx context.Context, signatureID, note string) error
InvalidateProjectRecord(ctx context.Context, signatureID, note string) error
UpdateEnvelopeDetails(ctx context.Context, signatureID, envelopeID string, signURL *string) (*models.Signature, error)
CreateSignature(ctx context.Context, signature *ItemSignature) error

GetSignature(ctx context.Context, signatureID string) (*models.Signature, error)
GetActivePullRequestMetadata(ctx context.Context, gitHubAuthorUsername, gitHubAuthorEmail string) (*ActivePullRequest, error)
Expand Down Expand Up @@ -134,6 +135,36 @@ func NewRepository(awsSession *session.Session, stage string, companyRepo compan
}
}

// CreateIndividualSignature creates a new individual signature
func (repo repository) CreateSignature(ctx context.Context, signature *ItemSignature) error {
f := logrus.Fields{
"functionName": "v1.signatures.repository.CreateIndividualSignature",
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
}

av, err := dynamodbattribute.MarshalMap(signature)
if err != nil {
log.WithFields(f).Warnf("error marshalling signature, error: %v", err)
return err
}

// Add the signature to the database
_, err = repo.dynamoDBClient.PutItem(&dynamodb.PutItemInput{
Item: av,
TableName: aws.String(repo.signatureTableName),
})

if err != nil {
log.WithFields(f).Warnf("error adding signature to database, error: %v", err)
return err
}

log.WithFields(f).Debugf("successfully added signature to database")

return nil

}

// GetGithubOrganizationsFromApprovalList returns a list of GH organizations stored in the approval list
func (repo repository) GetGithubOrganizationsFromApprovalList(ctx context.Context, signatureID string) ([]models.GithubOrg, error) {
f := logrus.Fields{
Expand Down
6 changes: 6 additions & 0 deletions cla-backend-go/signatures/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type SignatureService interface {
GetCompanyIDsWithSignedCorporateSignatures(ctx context.Context, claGroupID string) ([]SignatureCompanyID, error)
GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams) (*models.Signatures, error)
InvalidateProjectRecords(ctx context.Context, projectID, note string) (int, error)
CreateSignature(ctx context.Context, signature *ItemSignature) error

GetGithubOrganizationsFromApprovalList(ctx context.Context, signatureID string, githubAccessToken string) ([]models.GithubOrg, error)
AddGithubOrganizationToApprovalList(ctx context.Context, signatureID string, approvalListParams models.GhOrgWhitelist, githubAccessToken string) ([]models.GithubOrg, error)
Expand Down Expand Up @@ -163,6 +164,11 @@ func (s service) GetProjectCompanySignature(ctx context.Context, companyID, proj
return s.repo.GetProjectCompanySignature(ctx, companyID, projectID, approved, signed, nextKey, pageSize)
}

// CreateIndividualSignature creates a new individual signature
func (s service) CreateSignature(ctx context.Context, signature *ItemSignature) error {
return s.repo.CreateSignature(ctx, signature)
}

// GetProjectCompanySignatures returns the list of signatures associated with the specified project
func (s service) GetProjectCompanySignatures(ctx context.Context, params signatures.GetProjectCompanySignaturesParams) (*models.Signatures, error) {

Expand Down
117 changes: 76 additions & 41 deletions cla-backend-go/v2/sign/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,13 +420,13 @@ func (s *service) RequestIndividualSignature(ctx context.Context, input *models.
log.WithFields(f).Debugf("generating signature callback url...")
var callBackURL string

if strings.ToLower(input.ReturnURLType) == "github" {
if strings.ToLower(input.ReturnURLType) == utils.GitHubType {
callBackURL, err = s.getIndividualSignatureCallbackURL(ctx, *input.UserID, activeSignatureMetadata)
if err != nil {
log.WithFields(f).WithError(err).Warnf("unable to get signature callback url for user: %s", *input.UserID)
return nil, err
}
} else if strings.ToLower(input.ReturnURLType) == "gitlab" {
} else if strings.ToLower(input.ReturnURLType) == utils.GitLabLower {
callBackURL, err = s.getIndividualSignatureCallbackURLGitlab(ctx, *input.UserID, activeSignatureMetadata)
if err != nil {
log.WithFields(f).WithError(err).Warnf("unable to get signature callback url for user: %s", *input.UserID)
Expand All @@ -443,11 +443,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)
var signURL string
signURL, err = s.populateSignURL(ctx, latestSignature, callBackURL, "", "", false, "", "", defaultValues, preferredEmail)
if err != nil {
_, currentTime := utils.CurrentTime()
itemSignature := signatures.ItemSignature{
SignatureID: latestSignature.SignatureID,
DateModified: currentTime,
}
signURL, signErr := s.populateSignURL(ctx, &itemSignature, callBackURL, "", "", false, "", "", defaultValues, preferredEmail)
if signErr != nil {
log.WithFields(f).WithError(err).Warnf("unable to populate sign url for user: %s", *input.UserID)
return nil, err
return nil, signErr
}

return &models.IndividualSignatureOutput{
Expand Down Expand Up @@ -497,44 +501,50 @@ func (s *service) RequestIndividualSignature(ctx context.Context, input *models.
log.WithFields(f).Debugf("creating new signature object...")
signatureID := uuid.Must(uuid.NewV4()).String()
_, currentTime := utils.CurrentTime()
var acl string
if input.ReturnURLType == "github" {
acl = fmt.Sprintf("%s:%s", strings.ToLower(input.ReturnURLType), user.GithubID)
} else if input.ReturnURLType == "gitlab" {
acl = fmt.Sprintf("%s:%s", strings.ToLower(input.ReturnURLType), user.GitlabID)
}

signatureModel := &v1Models.Signature{
itemSignature := signatures.ItemSignature{
SignatureID: signatureID,
DateCreated: currentTime,
DateModified: currentTime,
SignatureSigned: false,
SignatureApproved: true,
SignatureDocumentMajorVersion: document.DocumentMajorVersion,
SignatureDocumentMinorVersion: document.DocumentMinorVersion,
SignatureReferenceID: *input.UserID,
SignatureReferenceType: "user",
ProjectID: *input.ProjectID,
SignatureReferenceName: user.Username,
SignatureType: utils.SignatureTypeCLA,
SignatureCreated: currentTime,
SignatureModified: currentTime,
SignatureReturnURLType: input.ReturnURLType,
SignatureProjectID: *input.ProjectID,
SignatureReturnURL: input.ReturnURL.String(),
SignatureCallbackURL: callBackURL,
SignatureReturnURLType: input.ReturnURLType,
}

// 9. Set signature ACL
log.WithFields(f).Debugf("setting signature ACL...")
signatureModel.SignatureACL = []v1Models.User{
*user,
SignatureReferenceType: "user",
SignatureACL: []string{acl},
SigtypeSignedApprovedID: fmt.Sprintf("%s#%v#%v#%s", utils.ClaTypeICLA, signed, approved, signatureID),
SignatureUserCompanyID: user.CompanyID,
SignatureReferenceNameLower: strings.ToLower(user.Username),
}

// 10. Populate sign url
log.WithFields(f).Debugf("populating sign url...")
var signURL string
signURL, err = s.populateSignURL(ctx, signatureModel, callBackURL, "", "", false, "", "", defaultValues, preferredEmail)
_, err = s.populateSignURL(ctx, &itemSignature, 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
}

log.WithFields(f).Debugf("Updated signature: %+v", signatureModel)
log.WithFields(f).Debugf("Updated signature: %+v", itemSignature)

return &models.IndividualSignatureOutput{
UserID: signatureModel.SignatureReferenceID,
ProjectID: signatureModel.ProjectID,
SignatureID: signatureModel.SignatureID,
SignURL: signURL,
UserID: itemSignature.SignatureReferenceID,
ProjectID: itemSignature.SignatureProjectID,
SignatureID: itemSignature.SignatureID,
SignURL: itemSignature.SignatureSignURL,
}, nil
}

Expand Down Expand Up @@ -647,7 +657,7 @@ func (s *service) getIndividualSignatureCallbackURL(ctx context.Context, userID

//nolint:gocyclo
func (s *service) populateSignURL(ctx context.Context,
latestSignature *v1Models.Signature, callbackURL string,
latestSignature *signatures.ItemSignature, callbackURL string,
authorityOrSignatoryName, authorityOrSignatoryEmail string,
sendAsEmail bool,
claManagerName, claManagerEmail string,
Expand Down Expand Up @@ -689,29 +699,29 @@ func (s *service) populateSignURL(ctx context.Context,

// Get the document template to sign
log.WithFields(f).Debugf("getting document template to sign...")
project, err = s.projectRepo.GetCLAGroupByID(ctx, latestSignature.ProjectID, DontLoadRepoDetails)
project, err = s.projectRepo.GetCLAGroupByID(ctx, latestSignature.SignatureProjectID, DontLoadRepoDetails)
if err != nil {
log.WithFields(f).WithError(err).Warnf("unable to lookup project by ID: %s", latestSignature.ProjectID)
log.WithFields(f).WithError(err).Warnf("unable to lookup project by ID: %s", latestSignature.SignatureProjectID)
return "", err
}

if project == nil {
log.WithFields(f).WithError(err).Warnf("unable to lookup project by ID: %s", latestSignature.ProjectID)
log.WithFields(f).WithError(err).Warnf("unable to lookup project by ID: %s", latestSignature.SignatureProjectID)
return "", errors.New("no project lookup error")
}

if signatureReferenceType == utils.SignatureReferenceTypeCompany {
log.WithFields(f).Debugf("loading project corporate document...")
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)
log.WithFields(f).WithError(err).Warnf("unable to lookup project corporate document for project: %s", latestSignature.SignatureProjectID)
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)
log.WithFields(f).WithError(err).Warnf("unable to lookup project individual document for project: %s", latestSignature.SignatureProjectID)
return "", err
}
}
Expand All @@ -727,7 +737,7 @@ func (s *service) populateSignURL(ctx context.Context,
}
}

documentID := "1"
documentID := uuid.Must(uuid.NewV4()).String()
tab := getTabsFromDocument(&document, documentID, defaultValues)

// # Create the envelope request object
Expand Down Expand Up @@ -877,6 +887,7 @@ func (s *service) populateSignURL(ctx context.Context,
// Webhook properties for callbacks after the user signs the document.
// Ensure that a webhook is returned on the status "Completed" where
// all signers on a document finish signing the document.
log.WithFields(f).Debugf("setting up webhook properties with callback url: %s", callbackURL)
recipientEvents := []DocuSignRecipientEvent{
{
EnvelopeEventStatusCode: "Completed",
Expand Down Expand Up @@ -963,16 +974,23 @@ func (s *service) populateSignURL(ctx context.Context,
// Save Envelope ID in signature.
log.WithFields(f).Debugf("saving signature to database...")
latestSignature.SignatureEnvelopeID = envelopeResponse.EnvelopeId
latestSignature.SignatureSignURL = *signatureSignURL

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.SignatureID)
return "", err
}

err = s.signatureService.CreateSignature(ctx, latestSignature)
if err != nil {
log.WithFields(f).WithError(err).Warnf("unable to save signature to database for user: %s", latestSignature.SignatureID)
return "", err
}

log.WithFields(f).Debug("signature saved to database")

log.WithFields(f).Debugf("populate_sign_url - complete: %s", *signatureSignURL)

return *signatureSignURL, nil
Expand All @@ -983,7 +1001,7 @@ type UserSignDetails struct {
userSignatureEmail string
}

func (s *service) populateUserDetails(ctx context.Context, signatureReferenceType string, latestSignature *v1Models.Signature, claManagerName, claManagerEmail string, sendAsEmail bool, preferredEmail string) (*UserSignDetails, error) {
func (s *service) populateUserDetails(ctx context.Context, signatureReferenceType string, latestSignature *signatures.ItemSignature, claManagerName, claManagerEmail string, sendAsEmail bool, preferredEmail string) (*UserSignDetails, error) {
f := logrus.Fields{
"functionName": "sign.populateUserDetails",
}
Expand Down Expand Up @@ -1375,30 +1393,40 @@ func (s *service) requestCorporateSignature(ctx context.Context, apiURL string,
}
callbackURL := s.getCorporateSignatureCallbackUrl(input.ProjectID, input.CompanyID)
var companySignature *v1Models.Signature
var itemSignature *signatures.ItemSignature
var signed bool
if len(companySignatures) > 0 {
companySignature = companySignatures[0]
itemSignature = &signatures.ItemSignature{
SignatureID: companySignature.SignatureID,
DateModified: companySignature.Modified,
}
signed = companySignature.SignatureSigned
approved = companySignature.SignatureApproved
} else {
// 5. if signature doesn't exists then Create new signature object
log.WithFields(f).Debugf("creating new signature object...")
signatureID := uuid.Must(uuid.NewV4()).String()
_, currentTime := utils.CurrentTime()

companySignature = &v1Models.Signature{
signed = false
approved = true
itemSignature = &signatures.ItemSignature{
SignatureID: signatureID,
SignatureDocumentMajorVersion: latestDocument.DocumentMajorVersion,
SignatureDocumentMinorVersion: latestDocument.DocumentMinorVersion,
SignatureReferenceID: comp.CompanyID,
SignatureReferenceType: "company",
SignatureReferenceName: comp.CompanyName,
ProjectID: input.ProjectID,
SignatureCreated: currentTime,
SignatureModified: currentTime,
SignatureProjectID: input.ProjectID,
DateCreated: currentTime,
DateModified: currentTime,
SignatureType: utils.SignatureTypeCCLA,
SignatoryName: signatoryName,
SigningEntityName: comp.SigningEntityName,
SignatureSigned: false,
SignatureApproved: true,
SigtypeSignedApprovedID: fmt.Sprintf("%s#%v#%v#%s", utils.SignatureTypeCCLA, signed, approved, signatureID),
}

}
companySignature.SignatureCallbackURL = callbackURL

Expand All @@ -1414,12 +1442,19 @@ 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, itemSignature, 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
}

companySignature, err = s.signatureService.GetCorporateSignature(ctx, input.ProjectID, input.CompanyID, &approved, &signed)

if err != nil {
log.WithFields(f).WithError(err).Warnf("unable to lookup user signatures by Company ID: %s, Project ID: %s", input.CompanyID, input.ProjectID)
return nil, err
}

return companySignature, nil
}

Expand Down

0 comments on commit 0006a6c

Please sign in to comment.