Skip to content

Commit

Permalink
[#4002] Feature/Docusign flow in Golang
Browse files Browse the repository at this point in the history
- Ported python flow for icla and ccla sign to golang
- Handled the new docusign auth flow

Signed-off-by: Harold Wanyama <hwanyama@contractor.linuxfoundation.org>
  • Loading branch information
nickmango committed Oct 10, 2023
1 parent 046d839 commit 1e706e9
Show file tree
Hide file tree
Showing 22 changed files with 1,989 additions and 114 deletions.
4 changes: 2 additions & 2 deletions 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.ClaV1ApiURL, v1CompanyRepo, v1CLAGroupRepo, v1ProjectClaGroupRepo, v1CompanyService, v2ClaGroupService)
v2SignService := sign.NewService(configFile.ClaV1ApiURL, v1CompanyRepo, v1CLAGroupRepo, v1ProjectClaGroupRepo, v1CompanyService, v2ClaGroupService, usersService, v1SignaturesService, storeRepository, v2RepositoriesService, githubOrganizationsService, gitlabOrganizationRepo)

sessionStore, err := dynastore.New(dynastore.Path("/"), dynastore.HTTPOnly(), dynastore.TableName(configFile.SessionStoreTableName), dynastore.DynamoDB(dynamodb.New(awsSession)))
if err != nil {
Expand Down Expand Up @@ -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, v2RepositoriesService, userRepo)
cla_groups.Configure(v2API, v2ClaGroupService, v1ProjectService, v1ProjectClaGroupRepo, eventsService)
v2GithubActivity.Configure(v2API, v2GithubActivityService)

Expand Down
41 changes: 39 additions & 2 deletions cla-backend-go/project/common/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ func GetCurrentDocument(ctx context.Context, docs []models.ClaGroupDocument) (mo
continue
}

// No previous, use the first...
if currentDoc == (models.ClaGroupDocument{}) {
if AreClaGroupDocumentsEqual(currentDoc, models.ClaGroupDocument{}) {
currentDoc = doc
currentDocVersion = version
currentDocDateTime = dateTime
Expand All @@ -127,3 +126,41 @@ func GetCurrentDocument(ctx context.Context, docs []models.ClaGroupDocument) (mo

return currentDoc, nil
}

// AreClaGroupDocumentsEqual compares two cla group document models
func AreClaGroupDocumentsEqual(doc1, doc2 models.ClaGroupDocument) bool {
// Compare each field individually, including the DocumentTabs slice
if doc1.DocumentAuthorName != doc2.DocumentAuthorName {
return false
}
if doc1.DocumentContentType != doc2.DocumentContentType {
return false
}
if doc1.DocumentCreationDate != doc2.DocumentCreationDate {
return false
}
if doc1.DocumentFileID != doc2.DocumentFileID {
return false
}
if doc1.DocumentLegalEntityName != doc2.DocumentLegalEntityName {
return false
}
if doc1.DocumentMajorVersion != doc2.DocumentMajorVersion {
return false
}
if doc1.DocumentMinorVersion != doc2.DocumentMinorVersion {
return false
}
if doc1.DocumentName != doc2.DocumentName {
return false
}
if doc1.DocumentPreamble != doc2.DocumentPreamble {
return false
}
if doc1.DocumentS3URL != doc2.DocumentS3URL {
return false
}

// If all comparisons passed, the structs are equal
return true
}
4 changes: 2 additions & 2 deletions cla-backend-go/project/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func (s ProjectService) GetCLAGroupCurrentICLATemplateURLByID(ctx context.Contex
}
}

if currentDoc == (models.ClaGroupDocument{}) {
if common.AreClaGroupDocumentsEqual(currentDoc, models.ClaGroupDocument{}) {
log.WithFields(f).WithError(err).Warn("problem determining current ICLA for this CLA Group")
return "", &utils.CLAGroupICLANotConfigured{
CLAGroupID: claGroupID,
Expand Down Expand Up @@ -288,7 +288,7 @@ func (s ProjectService) GetCLAGroupCurrentCCLATemplateURLByID(ctx context.Contex
}
}

if currentDoc == (models.ClaGroupDocument{}) {
if common.AreClaGroupDocumentsEqual(currentDoc, models.ClaGroupDocument{}) {
log.WithFields(f).WithError(err).Warn("problem determining current CCLA for this CLA Group")
return "", &utils.CLAGroupCCLANotConfigured{
CLAGroupID: claGroupID,
Expand Down
1 change: 1 addition & 0 deletions cla-backend-go/signatures/converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func (repo repository) buildProjectSignatureModels(ctx context.Context, results
SignatureReferenceNameLower: dbSignature.SignatureReferenceNameLower,
SignatureSigned: dbSignature.SignatureSigned,
SignatureApproved: dbSignature.SignatureApproved,
SignatureSignURL: dbSignature.SignatureSignURL,
SignatureMajorVersion: dbSignature.SignatureDocumentMajorVersion,
SignatureMinorVersion: dbSignature.SignatureDocumentMinorVersion,
Version: dbSignature.SignatureDocumentMajorVersion + "." + dbSignature.SignatureDocumentMinorVersion,
Expand Down
2 changes: 2 additions & 0 deletions cla-backend-go/signatures/dbmodels.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ type ItemSignature struct {
SignatureReferenceNameLower string `json:"signature_reference_name_lower"`
SignatureProjectID string `json:"signature_project_id"`
SignatureReferenceType string `json:"signature_reference_type"`
SignatureEnvelopeID string `json:"signature_envelope_id"`
SignatureType string `json:"signature_type"`
SignatureUserCompanyID string `json:"signature_user_ccla_company_id"`
SignatureSignURL string `json:"signature_sign_url"`
EmailApprovalList []string `json:"email_whitelist"`
EmailDomainApprovalList []string `json:"domain_whitelist"`
GitHubUsernameApprovalList []string `json:"github_whitelist"`
Expand Down
152 changes: 152 additions & 0 deletions cla-backend-go/signatures/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ type SignatureRepository interface {
EclaAutoCreate(ctx context.Context, signatureID string, autoCreateECLA bool) error
ActivateSignature(ctx context.Context, signatureID string) error
GetGitLabActiveMergeRequestMetadata(ctx context.Context, gitLabAuthorUsername, gitLabAuthorEmail string) (*ActiveGitLabPullRequest, error)
GetSignaturesByReference(ctx context.Context, referenceID, referenceType, projectID, userCCLACompanyID string, signatureSigned, signatureApproved bool) ([]*models.Signature, error)
CreateSignature(ctx context.Context, signature *models.Signature) (*models.Signature, error)
CreateOrUpdateSignature(ctx context.Context, signature *models.Signature) (*models.Signature, error)
}

type iclaSignatureWithDetails struct {
Expand Down Expand Up @@ -132,6 +135,155 @@ func NewRepository(awsSession *session.Session, stage string, companyRepo compan
}
}

func (repo repository) CreateOrUpdateSignature(ctx context.Context, signature *models.Signature) (*models.Signature, error) {
f := logrus.Fields{
"functionName": "v1.signatures.repository.CreateOrUpdateSignature",
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
"signature": signature,
}

// Check if the signature already exists in DynamoDB
existingSignature, err := repo.GetSignature(ctx, signature.SignatureID)
if err != nil {
log.WithFields(f).Warnf("error checking if signature exists, error: %v", err)
return nil, err
}

// If the signature exists, update it
if existingSignature != nil {
av, err := dynamodbattribute.MarshalMap(signature)
if err != nil {
log.WithFields(f).Warnf("error marshalling signature model, error: %v", err)
return nil, err
}

input := &dynamodb.PutItemInput{
Item: av,
TableName: aws.String(repo.signatureTableName),
}

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

// If the signature does not exist, create a new one
return repo.CreateSignature(ctx, signature)
}

func (repo repository) CreateSignature(ctx context.Context, signature *models.Signature) (*models.Signature, error) {
f := logrus.Fields{
"functionName": "v1.signatures.repository.CreateSignature",
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
"signature": signature,
}

av, err := dynamodbattribute.MarshalMap(signature)

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

input := &dynamodb.PutItemInput{
Item: av,
TableName: aws.String(repo.signatureTableName),
}

_, err = repo.dynamoDBClient.PutItem(input)

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

return signature, nil

}

// GetSignaturesByReference returns signatures by reference
func (repo repository) GetSignaturesByReference(ctx context.Context, referenceID, referenceType, projectID, userCCLACompanyID string, signatureSigned, signatureApproved bool) ([]*models.Signature, error) {
f := logrus.Fields{
"functionName": "v1.signatures.repository.GetSignaturesByReference",
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
"referenceID": referenceID,
"referenceType": referenceType,
"projectID": projectID,
"userCCLACompanyID": userCCLACompanyID,
"signatureSigned": signatureSigned,
"signatureApproved": signatureApproved,
}

log.WithFields(f).Debug("querying signature by reference...")
var filterAdded bool

// These are the keys we want to match for an ICLA Signature with a given CLA Group and User ID
condition := expression.Key("signature_reference_id").Equal(expression.Value(referenceID)).
And(expression.Key("signature_reference_type").Equal(expression.Value(referenceType)))

var filter expression.ConditionBuilder
filter = addAndCondition(filter, expression.Name("signature_project_id").Equal(expression.Value(projectID)), &filterAdded)
filter = addAndCondition(filter, expression.Name("signature_user_ccla_company_id").Equal(expression.Value(userCCLACompanyID)), &filterAdded)
filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(signatureSigned)), &filterAdded)
filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(signatureApproved)), &filterAdded)

// If no query option was provided for approved and signed and our configuration default is to only show active signatures then we add the required query filters
if signatureSigned == false && signatureApproved == false && config.GetConfig().SignatureQueryDefault == utils.SignatureQueryDefaultActive {
filterAdded = true
log.WithFields(f).Debug("adding filter signature_approved: true and signature_signed: true")
filter = addAndCondition(filter, expression.Name("signature_approved").Equal(expression.Value(true)), &filterAdded)
filter = addAndCondition(filter, expression.Name("signature_signed").Equal(expression.Value(true)), &filterAdded)
}

// Use the nice builder to create the expression
expr, err := expression.NewBuilder().
WithKeyCondition(condition).
WithFilter(filter).
WithProjection(buildProjection()).
Build()

if err != nil {
log.WithFields(f).Warnf("error building expression for signature query, error: %v", err)
return nil, err
}

// Assemble the query input parameters
queryInput := &dynamodb.QueryInput{
ExpressionAttributeNames: expr.Names(),
ExpressionAttributeValues: expr.Values(),
FilterExpression: expr.Filter(),
KeyConditionExpression: expr.KeyCondition(),
ProjectionExpression: expr.Projection(),
TableName: aws.String(repo.signatureTableName),
IndexName: aws.String(SignatureReferenceIndex),
}

// Make the DynamoDB Query API call
results, queryErr := repo.dynamoDBClient.Query(queryInput)
if queryErr != nil {
log.WithFields(f).Warnf("error retrieving signature by reference, error: %v", queryErr)
return nil, queryErr
}

// No match, didn't find it
if *results.Count == 0 {
return nil, nil
}

// Convert the list of DB models to a list of response models
signatureList, modelErr := repo.buildProjectSignatureModels(ctx, results, "", LoadACLDetails)
if modelErr != nil {
log.WithFields(f).Warnf("error converting DB model to response model for signature, error: %v", modelErr)
return nil, modelErr
}

return signatureList, 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
65 changes: 65 additions & 0 deletions cla-backend-go/signatures/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,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)
GetSignaturesByReference(ctx context.Context, referenceID, referenceType, projectID, userCCLACompanyID string, signatureSigned, signatureApproved bool) ([]*models.Signature, 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 @@ -69,9 +70,12 @@ type SignatureService interface {
GetClaGroupICLASignatures(ctx context.Context, claGroupID string, searchTerm *string, approved, signed *bool, pageSize int64, nextKey string, withExtraDetails bool) (*models.IclaSignatures, error)
GetClaGroupCCLASignatures(ctx context.Context, claGroupID string, approved, signed *bool) (*models.Signatures, error)
GetClaGroupCorporateContributors(ctx context.Context, claGroupID string, companyID *string, pageSize *int64, nextKey *string, searchTerm *string) (*models.CorporateContributorList, error)
GetLatestSignature(ctx context.Context, userID string, companyID string, projectID string) (*models.Signature, error)

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)
CreateSignature(ctx context.Context, signature *models.Signature) (*models.Signature, error)
CreateOrUpdateSignature(ctx context.Context, signature *models.Signature) (*models.Signature, error)
handleGitHubStatusUpdate(ctx context.Context, employeeUserModel *models.User) error
}

Expand Down Expand Up @@ -117,6 +121,62 @@ func NewService(repo SignatureRepository, companyService company.IService, users
}
}

// GetLatestSignatures returns the latest signatures for the specified user
func (s service) GetLatestSignature(ctx context.Context, userID string, companyID string, projectID string) (*models.Signature, error) {

f := logrus.Fields{
"functionName": "GetLatestSignature",
"userID": userID,
"companyID": companyID,
"projectID": projectID,
}

log.WithFields(f).Debug("querying for user signatures...")

if userID == "" || companyID == "" || projectID == "" {
return nil, errors.New("userID, companyID, and projectID cannot be empty")
}
signatures, err := s.GetSignaturesByReference(ctx, userID, "user", projectID, companyID, true, true)
if err != nil {
return nil, err
}

latest := &models.Signature{}

for _, sig := range signatures {
if latest == nil {
latest = sig
continue
}
if sig.SignatureMajorVersion > latest.SignatureMajorVersion {
latest = sig
continue
}
if sig.SignatureMajorVersion == latest.SignatureMajorVersion && sig.SignatureMinorVersion > latest.SignatureMinorVersion {
latest = sig
continue
}
}

if latest == nil {
return nil, errors.New("unable to locate latest signature")
}

log.WithFields(f).Debugf("latest signature: %+v", latest)

return latest, nil

}

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

func (s service) CreateOrUpdateSignature(ctx context.Context, signature *models.Signature) (*models.Signature, error) {
return s.repo.CreateOrUpdateSignature(ctx, signature)
}

// GetSignature returns the signature associated with the specified signature ID
func (s service) GetSignature(ctx context.Context, signatureID string) (*models.Signature, error) {
return s.repo.GetSignature(ctx, signatureID)
Expand All @@ -143,6 +203,11 @@ func (s service) GetProjectSignatures(ctx context.Context, params signatures.Get
return projectSignatures, nil
}

// GetSignaturesByReference returns the list of signatures associated with the specified reference
func (s service) GetSignaturesByReference(ctx context.Context, referenceID, referenceType, projectID, userCCLACompanyID string, signatureSigned, signatureApproved bool) ([]*models.Signature, error) {
return s.repo.GetSignaturesByReference(ctx, referenceID, referenceType, projectID, userCCLACompanyID, signatureSigned, signatureApproved)
}

// CreateProjectSummaryReport generates a project summary report based on the specified input
func (s service) CreateProjectSummaryReport(ctx context.Context, params signatures.CreateProjectSummaryReportParams) (*models.SignatureReport, error) {

Expand Down
3 changes: 3 additions & 0 deletions cla-backend-go/swagger/cla.v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2854,6 +2854,9 @@ definitions:

cla-group-document:
$ref: './common/cla-group-document.yaml'

cla-group-document-tab:
$ref: './common/cla-group-document-tab.yaml'

create-cla-group-template:
$ref: './common/create-cla-group-template.yaml'
Expand Down
Loading

0 comments on commit 1e706e9

Please sign in to comment.