Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/Docusign webhook #4196

Merged
merged 1 commit into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cla-backend-go/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ func server(localMode bool) http.Handler {
v2GithubActivityService := v2GithubActivity.NewService(gitV1Repository, githubOrganizationsRepo, eventsService, autoEnableService, emailService)

v2ClaGroupService := cla_groups.NewService(v1ProjectService, templateService, v1ProjectClaGroupRepo, v1ClaManagerService, v1SignaturesService, metricsRepo, gerritService, v1RepositoriesService, eventsService)
v2SignService := sign.NewService(configFile.ClaAPIV4Base, configFile.ClaV1ApiURL, v1CompanyRepo, v1CLAGroupRepo, v1ProjectClaGroupRepo, v1CompanyService, v2ClaGroupService, configFile.DocuSignPrivateKey, usersService, v1SignaturesService, storeRepository, v1RepositoriesService, githubOrganizationsService, gitlabOrganizationsService)
v2SignService := sign.NewService(configFile.ClaAPIV4Base, configFile.ClaV1ApiURL, v1CompanyRepo, v1CLAGroupRepo, v1ProjectClaGroupRepo, v1CompanyService, v2ClaGroupService, configFile.DocuSignPrivateKey, usersService, v1SignaturesService, storeRepository, v1RepositoriesService, githubOrganizationsService, gitlabOrganizationsService, configFile.CLALandingPage, configFile.CLALogoURL, emailService, eventsService)

sessionStore, err := dynastore.New(dynastore.Path("/"), dynastore.HTTPOnly(), dynastore.TableName(configFile.SessionStoreTableName), dynastore.DynamoDB(dynamodb.New(awsSession)))
if err != nil {
Expand Down
47 changes: 47 additions & 0 deletions cla-backend-go/emails/docusign_templates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright The Linux Foundation and each contributor to CommunityBridge.
// SPDX-License-Identifier: MIT

package emails

type DocumentSignedTemplateParams struct {
CommonEmailParams
CLAGroupTemplateParams
ICLA bool
PdfLink string
}

const (
// DocumentSignedTemplateName is email template name for DocumentSignedTemplate
DocumentSignedTemplateName = "DocumentSignedTemplate"

// DocumentSignedTemplate is email template for
DocumentSignedICLATemplate = `
<p>Hello {{.RecipientName}},</p>
<p>This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}}.</p>
<p>The CLA has now been signed. You can download the signed CLA as a PDF <a href="{{.PdfLink}}" target="_blank" alt="ICLA Document Link">here</a>.</p>
`

DocumentSignedCCLATemplate = `
<p>Hello {{.RecipientName}},</p>
<p>This is a notification email from EasyCLA regarding the project {{.Project.ExternalProjectName}}.</p>
<p>The CLA has now been signed. You can download the signed CLA as a PDF <a href="{{.PdfLink}}" target="_blank" alt="CCLA Document Link">here</a>, or from within the <a href="{{.CorporateConsole}}" target="_blank"> EasyCLA CLA Manager console </a>.</p>
`
)

// RenderDocumentSignedTemplate renders RenderDocumentSignedTemplate
func RenderDocumentSignedTemplate(svc EmailTemplateService, claGroupModelVersion, projectSFID string, params DocumentSignedTemplateParams) (string, error) {
claGroupParams, err := svc.GetCLAGroupTemplateParamsFromProjectSFID(claGroupModelVersion, projectSFID)
if err != nil {
return "", err
}

params.CLAGroupTemplateParams = claGroupParams
var template string
if params.ICLA {
template = DocumentSignedICLATemplate
} else {
template = DocumentSignedCCLATemplate
}

return RenderTemplate(claGroupModelVersion, DocumentSignedTemplateName, template, params)
}
18 changes: 18 additions & 0 deletions cla-backend-go/events/event_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,12 @@ type SignatureAutoCreateECLAUpdatedEventData struct {
AutoCreateECLA bool
}

type IndividualSignatureSignedEventData struct {
ProjectName string
Username string
ProjectID string
}

// GetEventDetailsString returns the details string for this event
func (ed *SignatureAutoCreateECLAUpdatedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) {

Expand Down Expand Up @@ -2725,3 +2731,15 @@ func (ed *SignatureAutoCreateECLAUpdatedEventData) GetEventSummaryString(args *L
data = data + "."
return data, false
}

func (ed *IndividualSignatureSignedEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) {
data := fmt.Sprintf("The user %s signed an individual CLA for project %s with project ID: %s",
args.LfUsername, ed.ProjectName, ed.ProjectID)
return data, false
}

func (ed *IndividualSignatureSignedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) {
data := fmt.Sprintf("The user %s signed an individual CLA for project %s",
args.LfUsername, ed.ProjectName)
return data, false
}
2 changes: 2 additions & 0 deletions cla-backend-go/events/event_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,6 @@ const (
ProjectServiceCLAEnabled = "project.service.cla.enabled"
ProjectServiceCLADisabled = "project.service.cla.disabled"
SignatureAutoCreateECLAUpdated = "signature.auto_create_ecla.updated"

IndividualSignatureSigned = "individual.signature.signed"
)
1 change: 1 addition & 0 deletions cla-backend-go/signatures/converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ func (repo repository) buildProjectSignatureModels(ctx context.Context, results
go func() {
defer swg.Done()
for _, userName := range sigACL {
log.WithFields(f).Debugf("looking up user by user name: %s", userName)
if loadACLDetails {
userModel, userErr := repo.usersRepo.GetUserByUserName(userName, true)
if userErr != nil {
Expand Down
1 change: 1 addition & 0 deletions cla-backend-go/signatures/dbmodels.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type ItemSignature struct {
UserDocusignName string `json:"user_docusign_name"`
UserDocusignDateSigned string `json:"user_docusign_date_signed"`
AutoCreateECLA bool `json:"auto_create_ecla"`
UserDocusignRawXML string `json:"user_docusign_raw_xml"`
}

// DBManagersModel is a database model for only the ACL/Manager column
Expand Down
2 changes: 1 addition & 1 deletion cla-backend-go/signatures/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ func Configure(api *operations.ClaAPI, service SignatureService, sessionStore *d
api.SignaturesGetUserSignaturesHandler = signatures.GetUserSignaturesHandlerFunc(func(params signatures.GetUserSignaturesParams, claUser *user.CLAUser) middleware.Responder {
reqID := utils.GetRequestID(params.XREQUESTID)
ctx := context.WithValue(context.Background(), utils.XREQUESTID, reqID) // nolint
userSignatures, err := service.GetUserSignatures(ctx, params)
userSignatures, err := service.GetUserSignatures(ctx, params, nil)
if err != nil {
log.Warnf("error retrieving user signatures for userID: %s, error: %+v", params.UserID, err)
return signatures.NewGetUserSignaturesBadRequest().WithXRequestID(reqID).WithPayload(errorResponse(err))
Expand Down
83 changes: 80 additions & 3 deletions cla-backend-go/signatures/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type SignatureRepository interface {
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
UpdateSignature(ctx context.Context, signatureID string, updates map[string]interface{}) error

GetSignature(ctx context.Context, signatureID string) (*models.Signature, error)
GetActivePullRequestMetadata(ctx context.Context, gitHubAuthorUsername, gitHubAuthorEmail string) (*ActivePullRequest, error)
Expand All @@ -89,7 +90,7 @@ type SignatureRepository interface {
CreateProjectCompanyEmployeeSignature(ctx context.Context, companyModel *models.Company, claGroupModel *models.ClaGroup, employeeUserModel *models.User) error
GetCompanySignatures(ctx context.Context, params signatures.GetCompanySignaturesParams, pageSize int64, loadACL bool) (*models.Signatures, error)
GetCompanyIDsWithSignedCorporateSignatures(ctx context.Context, claGroupID string) ([]SignatureCompanyID, error)
GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams, pageSize int64) (*models.Signatures, error)
GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams, pageSize int64, projectID *string) (*models.Signatures, error)
ProjectSignatures(ctx context.Context, projectID string) (*models.Signatures, error)
UpdateApprovalList(ctx context.Context, claManager *models.User, claGroupModel *models.ClaGroup, companyID string, params *models.ApprovalList, eventArgs *events.LogEventArgs) (*models.Signature, error)
AddCLAManager(ctx context.Context, signatureID, claManagerID string) (*models.Signature, error)
Expand Down Expand Up @@ -166,6 +167,73 @@ func (repo repository) CreateSignature(ctx context.Context, signature *ItemSigna

}

// UpdateSignature updates an existing signature
func (repo repository) UpdateSignature(ctx context.Context, signatureID string, updates map[string]interface{}) error {
f := logrus.Fields{
"functionName": "v1.signatures.repository.UpdateSignature",
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
"signatureID": signatureID,
}

if len(updates) == 0 {
log.WithFields(f).Warnf("no updates provided")
return errors.New("no updates provided")
}

var updateExpression strings.Builder
updateExpression.WriteString("SET ")
attributeValues := make(map[string]*dynamodb.AttributeValue)
expressionAttributeNames := make(map[string]*string)

count := 1
for attr, val := range updates {
attrPlaceholder := fmt.Sprintf("#A%d", count)
valPlaceholder := fmt.Sprintf(":v%d", count)

if count > 1 && count <= len(updates) {
updateExpression.WriteString(", ")
}
updateExpression.WriteString(fmt.Sprintf("%s = %s", attrPlaceholder, valPlaceholder))

expressionAttributeNames[attrPlaceholder] = aws.String(attr)
av, err := dynamodbattribute.Marshal(val)
if err != nil {
return err
}
attributeValues[valPlaceholder] = av

count++
}

log.WithFields(f).Debugf("updating signature using expression: %s", updateExpression.String())
log.WithFields(f).Debugf("expression attribute names : %+v", expressionAttributeNames)

input := &dynamodb.UpdateItemInput{
ExpressionAttributeValues: attributeValues,
TableName: aws.String(repo.signatureTableName),
Key: map[string]*dynamodb.AttributeValue{
"signature_id": {
S: aws.String(signatureID),
},
},
UpdateExpression: aws.String(updateExpression.String()),
ExpressionAttributeNames: expressionAttributeNames,
ReturnValues: aws.String("UPDATED_NEW"),
}

// perform the update
_, err := repo.dynamoDBClient.UpdateItem(input)
if err != nil {
log.WithFields(f).Warnf("error updating signature, error: %v", err)
return err
}

log.WithFields(f).Debugf("successfully updated signature")

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 Expand Up @@ -2659,16 +2727,23 @@ func (repo repository) GetCompanyIDsWithSignedCorporateSignatures(ctx context.Co
}

// GetUserSignatures returns a list of user signatures for the specified user
func (repo repository) GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams, pageSize int64) (*models.Signatures, error) {
func (repo repository) GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams, pageSize int64, projectID *string) (*models.Signatures, error) {
f := logrus.Fields{
"functionName": "v1.signatures.repository.GetUserSignatures",
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
}
// This is the keys we want to match
condition := expression.Key("signature_reference_id").Equal(expression.Value(params.UserID))

expressionBuilder := expression.NewBuilder().WithKeyCondition(condition).WithProjection(buildProjection())

if projectID != nil {
filterExpression := expression.Name("signature_project_id").Equal(expression.Value(*projectID))
expressionBuilder = expressionBuilder.WithFilter(filterExpression)
}

// Use the nice builder to create the expression
expr, err := expression.NewBuilder().WithKeyCondition(condition).WithProjection(buildProjection()).Build()
expr, err := expressionBuilder.Build()
if err != nil {
log.WithFields(f).Warnf("error building expression for user signature query, userID: %s, error: %v",
params.UserID, err)
Expand Down Expand Up @@ -2715,6 +2790,8 @@ func (repo repository) GetUserSignatures(ctx context.Context, params signatures.
return nil, errQuery
}

log.WithFields(f).Debugf("query results count: %d", len(results.Items))

// Convert the list of DB models to a list of response models
signatureList, modelErr := repo.buildProjectSignatureModels(ctx, results, "", LoadACLDetails)
if modelErr != nil {
Expand Down
17 changes: 12 additions & 5 deletions cla-backend-go/signatures/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ type SignatureService interface {
GetProjectCompanyEmployeeSignatures(ctx context.Context, params signatures.GetProjectCompanyEmployeeSignaturesParams, criteria *ApprovalCriteria) (*models.Signatures, error)
GetCompanySignatures(ctx context.Context, params signatures.GetCompanySignaturesParams) (*models.Signatures, error)
GetCompanyIDsWithSignedCorporateSignatures(ctx context.Context, claGroupID string) ([]SignatureCompanyID, error)
GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams) (*models.Signatures, error)
GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams, projectID *string) (*models.Signatures, error)
InvalidateProjectRecords(ctx context.Context, projectID, note string) (int, error)
CreateSignature(ctx context.Context, signature *ItemSignature) error
UpdateSignature(ctx context.Context, signatureID string, updates map[string]interface{}) 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 All @@ -72,6 +73,7 @@ type SignatureService interface {
CreateOrUpdateEmployeeSignature(ctx context.Context, claGroupModel *models.ClaGroup, companyModel *models.Company, corporateSignatureModel *models.Signature) ([]*models.User, error)
UpdateEnvelopeDetails(ctx context.Context, signatureID, envelopeID string, signURL *string) (*models.Signature, error)
handleGitHubStatusUpdate(ctx context.Context, employeeUserModel *models.User) error
ProcessEmployeeSignature(ctx context.Context, companyModel *models.Company, claGroupModel *models.ClaGroup, user *models.User) (*bool, error)
}

type service struct {
Expand Down Expand Up @@ -112,6 +114,11 @@ func (s service) GetSignature(ctx context.Context, signatureID string) (*models.
return s.repo.GetSignature(ctx, signatureID)
}

// UpdateSignature updates the specified signature
func (s service) UpdateSignature(ctx context.Context, signatureID string, updates map[string]interface{}) error {
return s.repo.UpdateSignature(ctx, signatureID, updates)
}

// GetIndividualSignature returns the signature associated with the specified CLA Group and User ID
func (s service) GetIndividualSignature(ctx context.Context, claGroupID, userID string, approved, signed *bool) (*models.Signature, error) {
return s.repo.GetIndividualSignature(ctx, claGroupID, userID, approved, signed)
Expand Down Expand Up @@ -228,15 +235,15 @@ func (s service) GetCompanyIDsWithSignedCorporateSignatures(ctx context.Context,
}

// GetUserSignatures returns the list of user signatures associated with the specified user
func (s service) GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams) (*models.Signatures, error) {
func (s service) GetUserSignatures(ctx context.Context, params signatures.GetUserSignaturesParams, projectID *string) (*models.Signatures, error) {

const defaultPageSize int64 = 10
var pageSize = defaultPageSize
if params.PageSize != nil {
pageSize = *params.PageSize
}

userSignatures, err := s.repo.GetUserSignatures(ctx, params, pageSize)
userSignatures, err := s.repo.GetUserSignatures(ctx, params, pageSize, projectID)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1160,7 +1167,7 @@ func (s service) hasUserSigned(ctx context.Context, user *models.User, projectID
return &hasSigned, &companyAffiliation, claGroupModelErr
}

employeeSigned, err := s.processEmployeeSignature(ctx, companyModel, claGroupModel, user)
employeeSigned, err := s.ProcessEmployeeSignature(ctx, companyModel, claGroupModel, user)

if err != nil {
log.WithFields(f).WithError(err).Warnf("problem looking up employee signature for company: %s", companyID)
Expand All @@ -1177,7 +1184,7 @@ func (s service) hasUserSigned(ctx context.Context, user *models.User, projectID
return &hasSigned, &companyAffiliation, nil
}

func (s service) processEmployeeSignature(ctx context.Context, companyModel *models.Company, claGroupModel *models.ClaGroup, user *models.User) (*bool, error) {
func (s service) ProcessEmployeeSignature(ctx context.Context, companyModel *models.Company, claGroupModel *models.ClaGroup, user *models.User) (*bool, error) {
f := logrus.Fields{
"functionName": "v2.signatures.service.processEmployeeSignature",
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
Expand Down
Loading
Loading