@@ -21,6 +21,7 @@ import (
2121 log "github.com/linuxfoundation/easycla/cla-backend-go/logging"
2222
2323 "github.com/aws/aws-sdk-go/aws"
24+ "github.com/aws/aws-sdk-go/aws/awserr"
2425 "github.com/aws/aws-sdk-go/aws/session"
2526 "github.com/aws/aws-sdk-go/service/dynamodb"
2627 "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
@@ -53,6 +54,8 @@ type IRepository interface { //nolint
5354 ApproveCompanyAccessRequest (ctx context.Context , companyInviteID string ) error
5455 RejectCompanyAccessRequest (ctx context.Context , companyInviteID string ) error
5556 UpdateCompanyAccessList (ctx context.Context , companyID string , companyACL []string ) error
57+ UpdateCompanySanctionStatus (ctx context.Context , companyID string , sanctioned bool , origin string ) error
58+ ClearCompanySanctionStatusIfSSS (ctx context.Context , companyID string ) (bool , error )
5659 IsCCLAEnabledForCompany (ctx context.Context , companyID string ) (bool , error )
5760}
5861
@@ -1276,7 +1279,119 @@ func (repo repository) UpdateCompanyAccessList(ctx context.Context, companyID st
12761279 return nil
12771280}
12781281
1279- // CreateCompany creates a new company record
1282+ // sanctionOriginSSS is the sanction_origin value written by the Sanctions Screening Service.
1283+ const sanctionOriginSSS = "sss"
1284+
1285+ // UpdateCompanySanctionStatus sets is_sanctioned and, when origin is non-empty, sanction_origin.
1286+ // Pass origin="sss" when flagging via SSS; pass origin="" for manual admin updates.
1287+ func (repo repository ) UpdateCompanySanctionStatus (ctx context.Context , companyID string , sanctioned bool , origin string ) error {
1288+ f := logrus.Fields {
1289+ "functionName" : "company.repository.UpdateCompanySanctionStatus" ,
1290+ utils .XREQUESTID : ctx .Value (utils .XREQUESTID ),
1291+ "companyID" : companyID ,
1292+ "sanctioned" : sanctioned ,
1293+ "origin" : origin ,
1294+ }
1295+
1296+ _ , now := utils .CurrentTime ()
1297+
1298+ names := map [string ]* string {
1299+ "#S" : aws .String ("is_sanctioned" ),
1300+ "#M" : aws .String ("date_modified" ),
1301+ }
1302+ values := map [string ]* dynamodb.AttributeValue {
1303+ ":s" : {BOOL : aws .Bool (sanctioned )},
1304+ ":m" : {S : aws .String (now )},
1305+ }
1306+ updateExpr := "SET #S = :s, #M = :m"
1307+
1308+ if origin != "" {
1309+ names ["#O" ] = aws .String ("sanction_origin" )
1310+ values [":o" ] = & dynamodb.AttributeValue {S : aws .String (origin )}
1311+ updateExpr += ", #O = :o"
1312+ } else {
1313+ // Manual/admin update: remove any stale SSS-set origin so the record becomes a
1314+ // sticky admin block (origin absent) that SSS will never auto-clear.
1315+ names ["#O" ] = aws .String ("sanction_origin" )
1316+ updateExpr += " REMOVE #O"
1317+ }
1318+
1319+ input := & dynamodb.UpdateItemInput {
1320+ ExpressionAttributeNames : names ,
1321+ ExpressionAttributeValues : values ,
1322+ TableName : aws .String (repo .companyTableName ),
1323+ Key : map [string ]* dynamodb.AttributeValue {
1324+ "company_id" : {S : aws .String (companyID )},
1325+ },
1326+ UpdateExpression : aws .String (updateExpr ),
1327+ }
1328+
1329+ // When SSS sets a block, never overwrite a manual/admin block (is_sanctioned=true
1330+ // with absent or non-"sss" origin). Only set the SSS flag when the company is
1331+ // currently unblocked or already SSS-blocked. A ConditionalCheckFailedException
1332+ // therefore means a manual/admin block is already in place and must be preserved.
1333+ sssSettingBlock := sanctioned && origin == sanctionOriginSSS
1334+ if sssSettingBlock {
1335+ values [":false" ] = & dynamodb.AttributeValue {BOOL : aws .Bool (false )}
1336+ input .ConditionExpression = aws .String ("attribute_not_exists(#S) OR #S = :false OR #O = :o" )
1337+ }
1338+
1339+ if _ , err := repo .dynamoDBClient .UpdateItem (input ); err != nil {
1340+ if sssSettingBlock {
1341+ if aerr , ok := err .(awserr.Error ); ok && aerr .Code () == dynamodb .ErrCodeConditionalCheckFailedException {
1342+ log .WithFields (f ).Debugf ("company %s already has a manual/admin sanction block; preserving it and not overwriting origin with sss" , companyID )
1343+ return nil
1344+ }
1345+ }
1346+ log .WithFields (f ).Warnf ("error updating company sanction status, error: %v" , err )
1347+ return err
1348+ }
1349+ return nil
1350+ }
1351+
1352+ // ClearCompanySanctionStatusIfSSS clears is_sanctioned only when sanction_origin="sss".
1353+ // It returns (cleared, err): cleared is true only when the conditional matched and the
1354+ // record was actually cleared; a ConditionalCheckFailedException (manual/absent origin)
1355+ // returns (false, nil) so callers can leave any manual/admin block in place.
1356+ func (repo repository ) ClearCompanySanctionStatusIfSSS (ctx context.Context , companyID string ) (bool , error ) {
1357+ f := logrus.Fields {
1358+ "functionName" : "company.repository.ClearCompanySanctionStatusIfSSS" ,
1359+ utils .XREQUESTID : ctx .Value (utils .XREQUESTID ),
1360+ "companyID" : companyID ,
1361+ }
1362+
1363+ _ , now := utils .CurrentTime ()
1364+
1365+ input := & dynamodb.UpdateItemInput {
1366+ TableName : aws .String (repo .companyTableName ),
1367+ Key : map [string ]* dynamodb.AttributeValue {
1368+ "company_id" : {S : aws .String (companyID )},
1369+ },
1370+ UpdateExpression : aws .String ("SET #S = :false, #M = :m REMOVE #O" ),
1371+ ConditionExpression : aws .String ("#O = :sss" ),
1372+ ExpressionAttributeNames : map [string ]* string {
1373+ "#S" : aws .String ("is_sanctioned" ),
1374+ "#M" : aws .String ("date_modified" ),
1375+ "#O" : aws .String ("sanction_origin" ),
1376+ },
1377+ ExpressionAttributeValues : map [string ]* dynamodb.AttributeValue {
1378+ ":false" : {BOOL : aws .Bool (false )},
1379+ ":m" : {S : aws .String (now )},
1380+ ":sss" : {S : aws .String (sanctionOriginSSS )},
1381+ },
1382+ }
1383+
1384+ if _ , err := repo .dynamoDBClient .UpdateItem (input ); err != nil {
1385+ if aerr , ok := err .(awserr.Error ); ok && aerr .Code () == dynamodb .ErrCodeConditionalCheckFailedException {
1386+ log .WithFields (f ).Debugf ("sanction_origin != sss for company %s; not auto-clearing (manual/admin block)" , companyID )
1387+ return false , nil
1388+ }
1389+ log .WithFields (f ).Warnf ("error clearing company sanction status: %v" , err )
1390+ return false , err
1391+ }
1392+ return true , nil
1393+ }
1394+
12801395func (repo repository ) CreateCompany (ctx context.Context , in * models.Company ) (* models.Company , error ) {
12811396 f := logrus.Fields {
12821397 "functionName" : "company.repository.CreateCompany" ,
0 commit comments