Skip to content
Closed
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/s3_upload/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func init() {
if err != nil {
log.Fatal(err)
}
signService = sign.NewService("", "", companyRepo, nil, nil, nil, nil, configFile.DocuSignPrivateKey, nil, nil, nil, nil, githubOrgService, nil, "", "", nil, nil, nil, nil, nil)
signService = sign.NewService("", "", companyRepo, nil, nil, nil, nil, configFile.DocuSignPrivateKey, nil, nil, nil, nil, githubOrgService, nil, "", "", nil, nil, nil, nil, nil, nil, configFile.SSS.Required)
// projectRepo = repository.NewRepository(awsSession, stage, nil, nil, nil)
utils.SetS3Storage(awsSession, configFile.SignatureFilesBucket)
}
Expand Down
27 changes: 26 additions & 1 deletion cla-backend-go/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import (

"github.com/linuxfoundation/easycla/cla-backend-go/api_logs"
"github.com/linuxfoundation/easycla/cla-backend-go/signatures"
"github.com/linuxfoundation/easycla/cla-backend-go/sss"
"github.com/linuxfoundation/easycla/cla-backend-go/telemetry"
v2Signatures "github.com/linuxfoundation/easycla/cla-backend-go/v2/signatures"

Expand Down Expand Up @@ -448,7 +449,31 @@ 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, configFile.CLALandingPage, configFile.CLALogoURL, emailService, eventsService, gitlabActivityService, gitlabApp, gerritService)

// Initialize SSS (Sanctions Screening Service) client if configured
var sssClient *sss.Client
if configFile.SSS.BaseURL != "" && configFile.SSS.Auth0Domain != "" && configFile.SSS.Auth0ClientID != "" && configFile.SSS.Auth0ClientSecret != "" && configFile.SSS.Auth0Audience != "" {
sssTimeout := time.Duration(configFile.SSS.RequestTimeoutSec) * time.Second
if sssTimeout <= 0 {
sssTimeout = 30 * time.Second // default timeout
}
sssConfig := sss.SSSConfig{
BaseURL: configFile.SSS.BaseURL,
Auth0Domain: configFile.SSS.Auth0Domain,
Auth0ClientID: configFile.SSS.Auth0ClientID,
Auth0ClientSecret: configFile.SSS.Auth0ClientSecret,
Auth0Audience: configFile.SSS.Auth0Audience,
Timeout: sssTimeout,
}
var sssErr error
sssClient, sssErr = sss.NewClient(sssConfig)
if sssErr != nil {
log.WithFields(f).WithError(sssErr).Warnf("failed to initialize SSS client, screening will be unavailable: %v", sssErr)
sssClient = nil
}
}

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, gitlabActivityService, gitlabApp, gerritService, sssClient, configFile.SSS.Required)

sessionStore, err := dynastore.New(dynastore.Path("/"), dynastore.HTTPOnly(), dynastore.TableName(configFile.SessionStoreTableName), dynastore.DynamoDB(dynamodb.New(awsSession)))
if err != nil {
Expand Down
63 changes: 62 additions & 1 deletion cla-backend-go/company/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type IRepository interface { //nolint
ApproveCompanyAccessRequest(ctx context.Context, companyInviteID string) error
RejectCompanyAccessRequest(ctx context.Context, companyInviteID string) error
UpdateCompanyAccessList(ctx context.Context, companyID string, companyACL []string) error
UpdateCompanySanctionStatus(ctx context.Context, companyID string, sanctioned bool) error
IsCCLAEnabledForCompany(ctx context.Context, companyID string) (bool, error)
}

Expand Down Expand Up @@ -1276,7 +1277,67 @@ func (repo repository) UpdateCompanyAccessList(ctx context.Context, companyID st
return nil
}

// CreateCompany creates a new company record
// UpdateCompanySanctionStatus updates the is_sanctioned flag for a company.
// It only performs the update if the value has changed to avoid unnecessary DynamoDB writes.
func (repo repository) UpdateCompanySanctionStatus(ctx context.Context, companyID string, sanctioned bool) error {
f := logrus.Fields{
"functionName": "company.repository.UpdateCompanySanctionStatus",
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
"companyID": companyID,
"sanctioned": sanctioned,
}

// Fetch current company to check if value has changed
currentCompany, err := repo.GetCompany(ctx, companyID)
if err != nil {
log.WithFields(f).Warnf("unable to fetch current company record to check sanction status, error: %v", err)
return err
}
if currentCompany == nil {
return fmt.Errorf("company not found: %s", companyID)
}

// Avoid unnecessary writes - only update if value has changed
if currentCompany.IsSanctioned == sanctioned {
log.WithFields(f).Debugf("sanction status unchanged (current=%v, new=%v), skipping update", currentCompany.IsSanctioned, sanctioned)
return nil
}

log.WithFields(f).Debugf("updating sanction status from %v to %v", currentCompany.IsSanctioned, sanctioned)

_, now := utils.CurrentTime()

input := &dynamodb.UpdateItemInput{
ExpressionAttributeNames: map[string]*string{
"#S": aws.String("is_sanctioned"),
"#M": aws.String("date_modified"),
},
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":s": {
BOOL: aws.Bool(sanctioned),
},
":m": {
S: aws.String(now),
},
},
TableName: aws.String(repo.companyTableName),
Key: map[string]*dynamodb.AttributeValue{
"company_id": {
S: aws.String(companyID),
},
},
UpdateExpression: aws.String("SET #S = :s, #M = :m"),
}

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

return nil
Comment on lines +1282 to +1338
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does aws-sdk-go v1.36.27 dynamodb client provide an UpdateItemWithContext method?

💡 Result:

No. In aws-sdk-go v1, the DynamoDB client’s context-aware method for UpdateItem is named UpdateItemWithContext (not UpdateItemWithContextWithContext / etc.), and aws-sdk-go v1 does not provide any additional method beyond that naming in the dynamodbiface interface. The interface includes UpdateItemWithContext(aws.Context, *dynamodb.UpdateItemInput,...request.Option), alongside UpdateItem(*dynamodb.UpdateItemInput). [1] So, the answer to “Does aws-sdk-go v1.36.27 dynamodb client provide an UpdateItemWithContext method?” is: Yes—UpdateItemWithContext exists for the DynamoDB client in aws-sdk-go v1 (as specified by the official dynamodbiface for the v1 SDK). [1]

Citations:


Propagate request ctx to the DynamoDB UpdateItem call.

UpdateCompanySanctionStatus accepts ctx but the DynamoDB write uses UpdateItem(input) (no context), so cancellations/deadlines won’t reach DynamoDB. Use the context-aware method supported by aws-sdk-go v1 (UpdateItemWithContext).

♻️ Use context-aware UpdateItem
-	_, err = repo.dynamoDBClient.UpdateItem(input)
+	_, err = repo.dynamoDBClient.UpdateItemWithContext(ctx, input)
 	if err != nil {
 		log.WithFields(f).Warnf("error updating company sanction status, error: %v", err)
 		return err
 	}

The read-before-write skip optimization and the BOOL/string attribute mapping look correct and consistent with the table model.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (repo repository) UpdateCompanySanctionStatus(ctx context.Context, companyID string, sanctioned bool) error {
f := logrus.Fields{
"functionName": "company.repository.UpdateCompanySanctionStatus",
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
"companyID": companyID,
"sanctioned": sanctioned,
}
// Fetch current company to check if value has changed
currentCompany, err := repo.GetCompany(ctx, companyID)
if err != nil {
log.WithFields(f).Warnf("unable to fetch current company record to check sanction status, error: %v", err)
return err
}
if currentCompany == nil {
return fmt.Errorf("company not found: %s", companyID)
}
// Avoid unnecessary writes - only update if value has changed
if currentCompany.IsSanctioned == sanctioned {
log.WithFields(f).Debugf("sanction status unchanged (current=%v, new=%v), skipping update", currentCompany.IsSanctioned, sanctioned)
return nil
}
log.WithFields(f).Debugf("updating sanction status from %v to %v", currentCompany.IsSanctioned, sanctioned)
_, now := utils.CurrentTime()
input := &dynamodb.UpdateItemInput{
ExpressionAttributeNames: map[string]*string{
"#S": aws.String("is_sanctioned"),
"#M": aws.String("date_modified"),
},
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":s": {
BOOL: aws.Bool(sanctioned),
},
":m": {
S: aws.String(now),
},
},
TableName: aws.String(repo.companyTableName),
Key: map[string]*dynamodb.AttributeValue{
"company_id": {
S: aws.String(companyID),
},
},
UpdateExpression: aws.String("SET #S = :s, #M = :m"),
}
_, err = repo.dynamoDBClient.UpdateItem(input)
if err != nil {
log.WithFields(f).Warnf("error updating company sanction status, error: %v", err)
return err
}
return nil
func (repo repository) UpdateCompanySanctionStatus(ctx context.Context, companyID string, sanctioned bool) error {
f := logrus.Fields{
"functionName": "company.repository.UpdateCompanySanctionStatus",
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
"companyID": companyID,
"sanctioned": sanctioned,
}
// Fetch current company to check if value has changed
currentCompany, err := repo.GetCompany(ctx, companyID)
if err != nil {
log.WithFields(f).Warnf("unable to fetch current company record to check sanction status, error: %v", err)
return err
}
if currentCompany == nil {
return fmt.Errorf("company not found: %s", companyID)
}
// Avoid unnecessary writes - only update if value has changed
if currentCompany.IsSanctioned == sanctioned {
log.WithFields(f).Debugf("sanction status unchanged (current=%v, new=%v), skipping update", currentCompany.IsSanctioned, sanctioned)
return nil
}
log.WithFields(f).Debugf("updating sanction status from %v to %v", currentCompany.IsSanctioned, sanctioned)
_, now := utils.CurrentTime()
input := &dynamodb.UpdateItemInput{
ExpressionAttributeNames: map[string]*string{
"`#S`": aws.String("is_sanctioned"),
"`#M`": aws.String("date_modified"),
},
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":s": {
BOOL: aws.Bool(sanctioned),
},
":m": {
S: aws.String(now),
},
},
TableName: aws.String(repo.companyTableName),
Key: map[string]*dynamodb.AttributeValue{
"company_id": {
S: aws.String(companyID),
},
},
UpdateExpression: aws.String("SET `#S` = :s, `#M` = :m"),
}
_, err = repo.dynamoDBClient.UpdateItemWithContext(ctx, input)
if err != nil {
log.WithFields(f).Warnf("error updating company sanction status, error: %v", err)
return err
}
return nil
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cla-backend-go/company/repository.go` around lines 1282 - 1338,
UpdateCompanySanctionStatus currently calls
repo.dynamoDBClient.UpdateItem(input) without ctx; change it to the
context-aware API repo.dynamoDBClient.UpdateItemWithContext(ctx, input) so the
passed ctx (from the function parameter and f log fields) propagates to DynamoDB
calls and respects cancellations/deadlines; update the call site that assigns _,
err = repo.dynamoDBClient.UpdateItem(...) to use UpdateItemWithContext(ctx, ...)
and keep the same error handling and return path.

}

func (repo repository) CreateCompany(ctx context.Context, in *models.Company) (*models.Company, error) {
f := logrus.Fields{
"functionName": "company.repository.CreateCompany",
Expand Down
14 changes: 14 additions & 0 deletions cla-backend-go/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ type Config struct {

// DocuSignPrivateKey is the private key for the DocuSign API
DocuSignPrivateKey string `json:"docuSignPrivateKey"`

// SSS (Sanctions Screening Service) configuration
SSS SSS `json:"sss"`
}

// SSS model for Sanctions Screening Service configuration
type SSS struct {
BaseURL string `json:"base_url"`
Auth0Domain string `json:"auth0_domain"`
Auth0ClientID string `json:"auth0_client_id"`
Auth0ClientSecret string `json:"auth0_client_secret"`
Auth0Audience string `json:"auth0_audience"`
RequestTimeoutSec int `json:"request_timeout_sec"`
Required bool `json:"required"`
}

// Auth0 model
Expand Down
1 change: 1 addition & 0 deletions cla-backend-go/config/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func loadLocalConfig(configFilePath string) (Config, error) {
}

localConfig := Config{}
localConfig.SSS.Required = true
err = json.Unmarshal(content, &localConfig)
if err != nil {
return Config{}, err
Expand Down
10 changes: 10 additions & 0 deletions cla-backend-go/config/ssm.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint
}
config := Config{}
config.SignatureQueryDefaultValue = "all"
config.SSS.Required = true

ssmClient := ssm.New(awsSession)

Expand Down Expand Up @@ -268,5 +269,14 @@ func loadSSMConfig(awsSession *session.Session, stage string) Config { //nolint
}
}

sssRequiredKey := fmt.Sprintf("cla-sss-required-%s", stage)
if value, err := getSSMString(ssmClient, sssRequiredKey); err != nil {
log.WithFields(f).WithError(err).Warnf("unable to read optional SSS required flag %s - defaulting to true", sssRequiredKey)
} else if boolVal, err := strconv.ParseBool(value); err != nil {
log.WithFields(f).WithError(err).Warnf("unable to convert %s value to a boolean - defaulting to true", sssRequiredKey)
} else {
config.SSS.Required = boolVal
}

return config
}
17 changes: 17 additions & 0 deletions cla-backend-go/v2/sign/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,23 @@ func (s service) hasUserSigned(ctx context.Context, user *models.User, projectID
log.WithFields(f).WithError(compModelErr).Warnf("problem looking up company: %s", companyID)
return &hasSigned, &companyAffiliation, compModelErr
}
if companyModel == nil {
compModelErr = fmt.Errorf("company not found: %s", companyID)
log.WithFields(f).WithError(compModelErr).Warnf("company record is nil for company: %s", companyID)
return &hasSigned, &companyAffiliation, compModelErr
}

// Check if company is sanctioned before allowing ECLA acknowledgement
sanctioned, sanctionErr := s.checkCompanyCompliance(ctx, companyModel)
if sanctionErr != nil {
log.WithFields(f).WithError(sanctionErr).Warnf("failed to check company compliance for company: %s", companyID)
return &hasSigned, &companyAffiliation, sanctionErr
}
if sanctioned {
sanctionedErr := fmt.Errorf("company %s is sanctioned", companyID)
log.WithFields(f).WithError(sanctionedErr).Error("company is sanctioned")
return &hasSigned, &companyAffiliation, sanctionedErr
}

// Load the CLA Group - make sure it is valid
claGroupModel, claGroupModelErr := s.claGroupService.GetCLAGroup(ctx, projectID)
Expand Down
Loading