Skip to content

Commit

Permalink
[communitybridge#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 Sep 29, 2023
1 parent 046d839 commit 159bc09
Show file tree
Hide file tree
Showing 15 changed files with 850 additions and 72 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
1 change: 1 addition & 0 deletions cla-backend-go/project/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type Service interface {
GetClaGroupByProjectSFID(ctx context.Context, projectSFID string, loadRepoDetails bool) (*models.ClaGroup, error)
SignedAtFoundationLevel(ctx context.Context, foundationSFID string) (bool, error)
GetCLAManagers(ctx context.Context, claGroupID string) ([]*models.ClaManagerUser, error)
// GetProjectIndividualDocuments(ctx context.Context, claGroupID string) (*models.ProjectICLAList, error)
}

// ProjectService project service data model
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 @@ -17,6 +17,7 @@ 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"`
EmailApprovalList []string `json:"email_whitelist"`
Expand Down
81 changes: 81 additions & 0 deletions cla-backend-go/signatures/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ 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)
}

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

// 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
54 changes: 54 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,6 +70,7 @@ 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)
Expand Down Expand Up @@ -117,6 +119,53 @@ 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

}

// 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 +192,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
46 changes: 21 additions & 25 deletions cla-backend-go/swagger/cla.v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4137,7 +4137,7 @@ paths:
- name: input
in: body
schema:
$ref: '#/definitions/icla-signature-input'
$ref: '#/definitions/individual-signature-input'
required: true
responses:
'200':
Expand Down Expand Up @@ -5527,37 +5527,27 @@ definitions:
corporate-contributor:
$ref: './common/corporate-contributor.yaml'

icla-signature-input:
individual-signature-input:
type: object
required:
- project_sfid
- company_sfid
properties:
project_sfid:
project_id:
description: 'The CLA group ID'
type: string
example: 'a0941000005ouJFAAY'
description: salesforce id of the project
company_sfid:
example: 'a1b86c26-d8e8-4fd8-9f8d-5c723d5dac9f'
user_id:
description: 'The User ID'
type: string
example: '0014100000Te0fMAAR'
description: salesforce id of the company
send_as_email:
type: boolean
example: false
description: send signing request as email. This should be set to true when requestor is not signatory.
authority_name:
example: 'a1b86c26-d8e8-4fd8-9f8d-5c723d5dac9f'
return_url_type:
description: 'The return URL type of the repository'
type: string
example: "Derk Miyamoto"
description: the name of the CLA signatory
minLength: 2
maxLength: 255
authority_email:
$ref: './common/properties/email.yaml'
description: the email of the CLA Signatory
enum:
- Gerrit
- Github
- GitLab
return_url:
description: 'The URL to return the user to after signing is complete.'
type: string
example: 'https://corporate.dev.lfcla.com/#/company/eb4d7d71-693f-4047-bf8d-10d0e7764969'
description: on signing the document, page will get redirected to this url. This is valid only when send_as_email is false
format: uri


Expand Down Expand Up @@ -5614,6 +5604,12 @@ definitions:
signature_id:
type: string
description: id of the signature
user_id:
type: string
description: uuid for the user
project_id:
type: string
description: the cla group ID
sign_url:
type: string
description: signing url
Expand Down
4 changes: 4 additions & 0 deletions cla-backend-go/swagger/common/signature.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ properties:
example: '2019-05-03T18:59:13.082304+0000'
minLength: 18
maxLength: 64
signatureSignUrl:
type: string
signatureEnvelopeId:
type: string
signatureSigned:
type: boolean
description: the signature signed flag - true or false value
Expand Down
32 changes: 32 additions & 0 deletions cla-backend-go/users/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import (

"github.com/communitybridge/easycla/cla-backend-go/events"
"github.com/communitybridge/easycla/cla-backend-go/gen/v1/models"
log "github.com/communitybridge/easycla/cla-backend-go/logging"
"github.com/communitybridge/easycla/cla-backend-go/user"
"github.com/communitybridge/easycla/cla-backend-go/utils"
"github.com/go-openapi/strfmt"
"github.com/sirupsen/logrus"
)

// Service interface for users
Expand All @@ -26,6 +30,7 @@ type Service interface {
GetUserByGitLabUsername(gitlabUsername string) (*models.User, error)
SearchUsers(field string, searchTerm string, fullMatch bool) (*models.Users, error)
UpdateUserCompanyID(userID, companyID, note string) error
GetUserEmail(user *models.User, preferredEmail string) (string, error)
}

type service struct {
Expand All @@ -41,6 +46,33 @@ func NewService(repo UserRepository, events events.Service) Service {
}
}

func (s service) GetUserEmail(user *models.User, preferredEmail string) (string, error) {
f := logrus.Fields{
"functionName": "GetUserEmail",
"userID": user.UserID,
"preferredEmail": preferredEmail,
}

if preferredEmail != "" && user.LfEmail != "" && user.LfEmail == strfmt.Email(preferredEmail) {
log.WithFields(f).Debug("user email matches preferred email")
return user.LfEmail.String(), nil
}

if preferredEmail != "" && utils.StringInSlice(preferredEmail, user.Emails) {
log.WithFields(f).Debug("user email matches preferred email")
return preferredEmail, nil
}

if len(user.Emails) > 0 {
log.WithFields(f).Debug("returning first user email")
return user.Emails[0], nil
}

log.WithFields(f).Debug("unable to find user email")
return "", errors.New("unable to find user email")

}

// CreateUser attempts to create a new user based on the specified model
func (s service) CreateUser(user *models.User, claUser *user.CLAUser) (*models.User, error) {
userModel, err := s.repo.CreateUser(user)
Expand Down
6 changes: 3 additions & 3 deletions cla-backend-go/v2/main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package main
import (
"fmt"

"github.com/communitybridge/easycla/cla-backend-go/v2/docusign_auth"
docusignauth "github.com/communitybridge/easycla/cla-backend-go/v2/docusign_auth"
)

func main() {
integrationKey := "557677f2-1e2f-4955-aaa6-1ef44630e01d"
userID := "3a1d118f-3083-4c25-8306-11b7400f7c03"
privateKey :=`
privateKey := `
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAh0M2mIGaJjP8S/FxZR7nRsatCR/KpCPBFBbxalZffykqtTID
KNeDhJ5RvJKAVoJlLaLoUYSYloVaeSAwQdbn4F+Lsnll3mCGocwdl/W8998Lc/Ln
Expand Down Expand Up @@ -44,4 +44,4 @@ func main() {
}

fmt.Println(token)
}
}
Loading

0 comments on commit 159bc09

Please sign in to comment.