Skip to content

Commit 02b15e7

Browse files
Followup SSS integration
Signed-off-by: Lukasz Gryglicki <lgryglicki@cncf.io> Assisted by [OpenAI](https://platform.openai.com/) Assisted by [GitHub Copilot](https://github.com/features/copilot) Assisted by [Claude](https://claude.ai)
1 parent 2b40b7c commit 02b15e7

7 files changed

Lines changed: 114 additions & 8 deletions

File tree

cla-backend-go/company/projections.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ func buildCompanyProjection() expression.ProjectionBuilder {
1919
expression.Name("date_modified"),
2020
expression.Name("note"),
2121
expression.Name("is_sanctioned"),
22+
expression.Name("sanction_origin"),
2223
expression.Name("version"),
2324
)
2425
}

cla-backend-go/company/repository.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,7 +1318,23 @@ func (repo repository) UpdateCompanySanctionStatus(ctx context.Context, companyI
13181318
UpdateExpression: aws.String(updateExpr),
13191319
}
13201320

1321+
// When SSS sets a block, never overwrite a manual/admin block (is_sanctioned=true
1322+
// with absent or non-"sss" origin). Only set the SSS flag when the company is
1323+
// currently unblocked or already SSS-blocked. A ConditionalCheckFailedException
1324+
// therefore means a manual/admin block is already in place and must be preserved.
1325+
sssSettingBlock := sanctioned && origin == "sss"
1326+
if sssSettingBlock {
1327+
values[":false"] = &dynamodb.AttributeValue{BOOL: aws.Bool(false)}
1328+
input.ConditionExpression = aws.String("attribute_not_exists(#S) OR #S = :false OR #O = :o")
1329+
}
1330+
13211331
if _, err := repo.dynamoDBClient.UpdateItem(input); err != nil {
1332+
if sssSettingBlock {
1333+
if aerr, ok := err.(awserr.Error); ok && aerr.Code() == dynamodb.ErrCodeConditionalCheckFailedException {
1334+
log.WithFields(f).Debugf("company %s already has a manual/admin sanction block; preserving it and not overwriting origin with sss", companyID)
1335+
return nil
1336+
}
1337+
}
13221338
log.WithFields(f).Warnf("error updating company sanction status, error: %v", err)
13231339
return err
13241340
}

cla-backend-go/signatures/service.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,14 @@ func (s service) CreateOrUpdateEmployeeSignature(ctx context.Context, claGroupMo
833833
"companyID": companyModel.CompanyID,
834834
}
835835

836+
// Sanctions gate: never auto-create employee (ECLA) signatures for a sanctioned
837+
// company. is_sanctioned is the persisted gate (SSS origin="sss" or a manual/admin
838+
// block); both must block ECLA creation (auto-create toggle and approval-list edits).
839+
if companyModel.IsSanctioned {
840+
log.WithFields(f).Warnf("company %s is sanctioned (origin=%q); refusing to auto-create employee (ECLA) signatures", companyModel.CompanyID, companyModel.SanctionOrigin)
841+
return nil, fmt.Errorf("company %s is sanctioned; employee (ECLA) signatures cannot be created", companyModel.CompanyID)
842+
}
843+
836844
// Most of the following business logic is all the same - however, we need to handle the different types of approval lists entries and process them in the same way
837845
// We build a list of users to process - this is a list of simple user models that contain the email, GitHub username, and GitLab username - typically only one of the values in the model will be set
838846
userList, userErr := s.createOrGetEmployeeModels(ctx, claGroupModel, companyModel, corporateSignatureModel)
@@ -1517,6 +1525,17 @@ func (s service) ProcessEmployeeSignature(ctx context.Context, companyModel *mod
15171525
"projectID": claGroupModel.ProjectID,
15181526
"userID": user.UserID,
15191527
}
1528+
1529+
// Sanctions gate: a sanctioned company's employees are not authorized. is_sanctioned
1530+
// is the persisted gate (SSS origin="sss" or a manual/admin block); honor it here so
1531+
// employee-acknowledgement (ECLA) authorization fails for sanctioned companies on
1532+
// GitHub PR checks and authorization queries.
1533+
if companyModel.IsSanctioned {
1534+
log.WithFields(f).Warnf("company %s is sanctioned (origin=%q); employee acknowledgement not authorized", companyModel.CompanyID, companyModel.SanctionOrigin)
1535+
notSigned := false
1536+
return &notSigned, nil
1537+
}
1538+
15201539
var wg sync.WaitGroup
15211540
resultChannel := make(chan *EmployeeModel)
15221541
errorChannel := make(chan error)

cla-backend-go/v2/gitlab-activity/service.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,13 +516,21 @@ func (s *service) isSigned(ctx context.Context, userModel *models.User, claGroup
516516
}
517517

518518
companyID := userModel.CompanyID
519-
_, err = s.companyRepository.GetCompany(ctx, companyID)
519+
companyModel, err := s.companyRepository.GetCompany(ctx, companyID)
520520
if err != nil {
521521
msg := fmt.Sprintf("can't load company record: %s for user: %s (%s), error: %v", companyID, userModel.Username, userModel.UserID, err)
522522
log.WithFields(f).Errorf("%s", msg)
523523
return false, fmt.Errorf("%s", msg)
524524
}
525525

526+
// Sanctions gate: a sanctioned company's employees are not authorized. Honor the
527+
// persisted is_sanctioned gate (SSS origin="sss" or a manual/admin block) so GitLab
528+
// MR checks fail for sanctioned companies.
529+
if companyModel != nil && companyModel.IsSanctioned {
530+
log.WithFields(f).Warnf("company %s is sanctioned (origin=%q); GitLab contributor not authorized", companyID, companyModel.SanctionOrigin)
531+
return false, fmt.Errorf("company %s is sanctioned", companyID)
532+
}
533+
526534
corporateSignature, err := s.signatureRepository.GetCorporateSignature(ctx, claGroupID, companyID, aws.Bool(true), aws.Bool(true))
527535
if err != nil {
528536
msg := fmt.Sprintf("can't load company signature record for company: %s for user : %s (%s), error : %v", companyID, userModel.Username, userModel.UserID, err)

cla-backend-go/v2/sign/service.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,17 @@ func (s *service) SignedCorporateCallback(ctx context.Context, payload []byte, c
11491149
return err
11501150
}
11511151

1152+
// Sanctions gate: re-screen the company before finalizing the CCLA. A company can
1153+
// become blocked (manual/admin or SSS) between the DocuSign request and this
1154+
// completion callback; do not finalize a corporate CLA for a sanctioned company.
1155+
if sanctioned, complianceErr := s.checkCompanyCompliance(ctx, companyModel); complianceErr != nil {
1156+
log.WithFields(f).WithError(complianceErr).Warnf("company compliance check failed in corporate callback for company %s; not finalizing CCLA", companyID)
1157+
return complianceErr
1158+
} else if sanctioned {
1159+
log.WithFields(f).Warnf("company %s is sanctioned; refusing to finalize corporate CLA in callback", companyID)
1160+
return fmt.Errorf("company %s is sanctioned; corporate CLA cannot be finalized", companyID)
1161+
}
1162+
11521163
// Assumme only one signature per company/project
11531164
var signatureID string
11541165
var signature *v1Models.Signature
@@ -2970,12 +2981,19 @@ func (s *service) GetUserActiveSignature(ctx context.Context, userID string) (*m
29702981
// checkCompanyCompliance queries the Sanctions Screening Service for the given company
29712982
// and persists the result. Returns (sanctioned, error).
29722983
func (s *service) checkCompanyCompliance(ctx context.Context, company *v1Models.Company) (bool, error) {
2984+
sssMode := "optional"
2985+
if s.sssRequired {
2986+
sssMode = "required"
2987+
}
29732988
f := logrus.Fields{
2974-
"functionName": "sign.checkCompanyCompliance",
2975-
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
2976-
"companyID": company.CompanyID,
2977-
"companyName": company.CompanyName,
2989+
"functionName": "sign.checkCompanyCompliance",
2990+
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
2991+
"companyID": company.CompanyID,
2992+
"companyName": company.CompanyName,
2993+
"companyExternalID": company.CompanyExternalID,
2994+
"sssMode": sssMode,
29782995
}
2996+
log.WithFields(f).Debugf("starting company sanctions screening (mode=%s)", sssMode)
29792997

29802998
// Short-circuit for manually/admin-set blocks (sanction_origin != "sss" or no origin).
29812999
// SSS-origin blocks fall through so a now-clean result can clear them.
@@ -3052,10 +3070,12 @@ func (s *service) checkCompanyCompliance(ctx context.Context, company *v1Models.
30523070
req.SFDCID = company.CompanyExternalID
30533071
}
30543072

3073+
log.WithFields(f).Infof("calling SSS GetOrganizationStatus: domain=%s, orgName=%s, sfdcID=%q (mode=%s)", req.Domain, req.OrgName, req.SFDCID, sssMode)
30553074
result, err := s.sssClient.GetOrganizationStatus(ctx, req)
30563075
if err != nil {
30573076
return s.handleSSSError(f, company.CompanyID, err)
30583077
}
3078+
log.WithFields(f).Infof("SSS GetOrganizationStatus result for company %s: status=%q (domain=%s, mode=%s)", company.CompanyID, result.Status, req.Domain, sssMode)
30593079

30603080
sanctioned := result.Status == sss.StatusFlagged
30613081

@@ -3073,9 +3093,16 @@ func (s *service) checkCompanyCompliance(ctx context.Context, company *v1Models.
30733093
}
30743094
} else {
30753095
// Clear only when previously set by SSS; manual blocks are left untouched.
3096+
// ClearCompanySanctionStatusIfSSS treats a conditional-check failure (manual
3097+
// block / nothing to clear) as a no-op, so a non-nil error here is a real
3098+
// persistence failure. In required mode we fail closed rather than allow with a
3099+
// stale persisted sanction still in place.
30763100
log.WithFields(f).Debugf("SSS returned clean status for company %s; attempting conditional clear", company.CompanyID)
30773101
if clearErr := s.companyRepo.ClearCompanySanctionStatusIfSSS(ctx, company.CompanyID); clearErr != nil {
30783102
log.WithFields(f).WithError(clearErr).Warnf("failed to conditionally clear sanction status for company %s", company.CompanyID)
3103+
if s.sssRequired {
3104+
return false, fmt.Errorf("checkCompanyCompliance: SSS returned clean but clearing the persisted sanction failed for company %s: %w", company.CompanyID, clearErr)
3105+
}
30793106
}
30803107
}
30813108

cla-backend-legacy/internal/api/handlers.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8896,6 +8896,12 @@ func (h *Handlers) checkCompanyCompliance(ctx context.Context, company map[strin
88968896
companyName := getAttrString(company, "company_name")
88978897
companyExternalID := getAttrString(company, "company_external_id")
88988898

8899+
sssMode := "optional"
8900+
if h.sssRequired {
8901+
sssMode = "required"
8902+
}
8903+
logging.Debugf("starting company sanctions screening for company %s (external_id=%s, mode=%s)", companyID, companyExternalID, sssMode)
8904+
88998905
// Short-circuit for manually/admin-set blocks (sanction_origin != "sss" or no origin).
89008906
// SSS-origin blocks fall through so a now-clean result can clear them.
89018907
isSanctioned := false
@@ -8959,10 +8965,12 @@ func (h *Handlers) checkCompanyCompliance(ctx context.Context, company map[strin
89598965
req.SFDCID = companyExternalID
89608966
}
89618967

8968+
logging.Infof("calling SSS GetOrganizationStatus for company %s: domain=%s, orgName=%s, sfdcID=%q (mode=%s)", companyID, req.Domain, req.OrgName, req.SFDCID, sssMode)
89628969
result, err := h.sssClient.GetOrganizationStatus(ctx, req)
89638970
if err != nil {
89648971
return h.handleSSSError(ctx, companyID, err)
89658972
}
8973+
logging.Infof("SSS GetOrganizationStatus result for company %s: status=%q (domain=%s, mode=%s)", companyID, result.Status, req.Domain, sssMode)
89668974

89678975
sanctioned := result.Status == sss.StatusFlagged
89688976

@@ -8980,9 +8988,15 @@ func (h *Handlers) checkCompanyCompliance(ctx context.Context, company map[strin
89808988
}
89818989
} else {
89828990
// Clear only when previously set by SSS; manual blocks are left untouched.
8991+
// ClearCompanySanctionStatusIfSSS treats a conditional-check failure as a no-op,
8992+
// so a non-nil error here is a real persistence failure. In required mode we fail
8993+
// closed rather than allow with a stale persisted sanction still in place.
89838994
logging.Debugf("SSS returned clean status for company %s; attempting conditional clear", companyID)
89848995
if clearErr := h.companies.ClearCompanySanctionStatusIfSSS(ctx, companyID); clearErr != nil {
89858996
logging.Warnf("failed to conditionally clear sanction status for company %s: %v", companyID, clearErr)
8997+
if h.sssRequired {
8998+
return false, fmt.Errorf("checkCompanyCompliance: SSS returned clean but clearing the persisted sanction failed for company %s: %w", companyID, clearErr)
8999+
}
89869000
}
89879001
}
89889002

cla-backend-legacy/internal/store/companies.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,16 +166,37 @@ func (s *CompaniesStore) UpdateCompanySanctionStatus(ctx context.Context, compan
166166
updateExpr += ", #O = :o"
167167
}
168168

169-
_, err := s.client.UpdateItem(ctx, &dynamodb.UpdateItemInput{
169+
input := &dynamodb.UpdateItemInput{
170170
TableName: aws.String(s.table),
171171
Key: map[string]types.AttributeValue{
172172
"company_id": &types.AttributeValueMemberS{Value: companyID},
173173
},
174174
UpdateExpression: aws.String(updateExpr),
175175
ExpressionAttributeNames: names,
176176
ExpressionAttributeValues: values,
177-
})
178-
return err
177+
}
178+
179+
// When SSS sets a block, never overwrite a manual/admin block (is_sanctioned=true
180+
// with absent or non-"sss" origin). Only set the SSS flag when the company is
181+
// currently unblocked or already SSS-blocked; a ConditionalCheckFailedException
182+
// means a manual/admin block is already present and must be preserved.
183+
sssSettingBlock := sanctioned && origin == "sss"
184+
if sssSettingBlock {
185+
values[":false"] = &types.AttributeValueMemberBOOL{Value: false}
186+
input.ConditionExpression = aws.String("attribute_not_exists(#S) OR #S = :false OR #O = :o")
187+
}
188+
189+
_, err := s.client.UpdateItem(ctx, input)
190+
if err != nil {
191+
if sssSettingBlock {
192+
var condErr *types.ConditionalCheckFailedException
193+
if errors.As(err, &condErr) {
194+
return nil // Preserve the existing manual/admin block
195+
}
196+
}
197+
return err
198+
}
199+
return nil
179200
}
180201

181202
// ClearCompanySanctionStatusIfSSS clears is_sanctioned only when sanction_origin="sss".

0 commit comments

Comments
 (0)