Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -288,3 +288,4 @@ spans*.json
*.out
CLAUDE.md
.claude/*
/*user_emails.json
3 changes: 2 additions & 1 deletion cla-backend-go/approval_list/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package approval_list
import (
"errors"
"fmt"
"strings"

models2 "github.com/linuxfoundation/easycla/cla-backend-go/project/models"

Expand Down Expand Up @@ -86,7 +87,7 @@ func (repo repository) AddCclaApprovalRequest(company *models.Company, project *
addStringAttribute(input.Item, "project_id", project.ProjectID)
addStringAttribute(input.Item, "project_name", project.ProjectName)
addStringAttribute(input.Item, "user_id", user.UserID)
addStringSliceAttribute(input.Item, "user_emails", []string{requesterEmail})
addStringSliceAttribute(input.Item, "user_emails", []string{strings.ToLower(strings.TrimSpace(requesterEmail))})
addStringAttribute(input.Item, "user_name", requesterName)
Comment thread
lukaszgryglicki marked this conversation as resolved.
addStringAttribute(input.Item, "user_github_id", user.GithubID)
addStringAttribute(input.Item, "user_github_username", user.GithubUsername)
Expand Down
1 change: 1 addition & 0 deletions cla-backend-go/signatures/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -3342,6 +3342,7 @@ func (repo repository) UpdateApprovalList(ctx context.Context, claManager *model
for _, email := range params.RemoveEmailApprovalList {
go func(email string) {
defer wg.Done()
email = strings.ToLower(strings.TrimSpace(email))
var iclas []*models.IclaSignature
var eclas []*models.Signature
Comment thread
lukaszgryglicki marked this conversation as resolved.
log.WithFields(f).Debugf("getting cla user record for email: %s ", email)
Expand Down
31 changes: 28 additions & 3 deletions cla-backend-go/users/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ func (repo repository) CreateUser(user *models.User) (*models.User, error) {
}
}

user.Emails = normalizeEmails(user.Emails)
if len(user.Emails) > 0 {
attributes["user_emails"] = &dynamodb.AttributeValue{
SS: utils.ArrayStringPointer(user.Emails),
Expand Down Expand Up @@ -386,9 +387,10 @@ func (repo repository) Save(user *models.UserUpdate) (*models.User, error) {
}

if user.Emails != nil {
log.WithFields(f).Debugf("building query - adding user_emails: %v", user.Emails)
normalized := normalizeEmails(user.Emails)
log.WithFields(f).Debugf("building query - adding user_emails: %v", normalized)
expressionAttributeNames["#UES"] = aws.String("user_emails")
expressionAttributeValues[":ues"] = &dynamodb.AttributeValue{SS: aws.StringSlice(user.Emails)}
expressionAttributeValues[":ues"] = &dynamodb.AttributeValue{SS: aws.StringSlice(normalized)}
Comment thread
lukaszgryglicki marked this conversation as resolved.
updateExpression = updateExpression + " #UES = :ues, "
Comment thread
lukaszgryglicki marked this conversation as resolved.
}
Comment thread
lukaszgryglicki marked this conversation as resolved.

Expand Down Expand Up @@ -808,6 +810,8 @@ func (repo repository) GetUsersByEmail(userEmail string) ([]*models.User, error)
"userEmail": userEmail,
}

userEmail = strings.ToLower(strings.TrimSpace(userEmail))

// This is the filter we want to match
filter := expression.Name("user_emails").Contains(userEmail)

Expand All @@ -817,7 +821,7 @@ func (repo repository) GetUsersByEmail(userEmail string) ([]*models.User, error)
// Use the nice builder to create the expression
expr, err := expression.NewBuilder().WithFilter(filter).WithProjection(projection).Build()
if err != nil {
log.WithFields(f).Warnf("error building expression for lf_email : %s, error: %v", userEmail, err)
log.WithFields(f).Warnf("error building expression for user_emails : %s, error: %v", userEmail, err)
return nil, err
}

Expand Down Expand Up @@ -883,6 +887,27 @@ func (repo repository) GetUsersByEmail(userEmail string) ([]*models.User, error)
return users, nil
}

// normalizeEmails lower-cases, trims and de-duplicates emails (DynamoDB string sets reject duplicates).
func normalizeEmails(emails []string) []string {
if emails == nil {
return nil
}
seen := make(map[string]struct{}, len(emails))
out := make([]string, 0, len(emails))
for _, email := range emails {
email = strings.ToLower(strings.TrimSpace(email))
if email == "" {
continue
}
if _, ok := seen[email]; ok {
continue
}
seen[email] = struct{}{}
out = append(out, email)
}
return out
}

// GetUsersByLFEmail fetches the user record by email
func (repo repository) GetUsersByLFEmail(userEmail string) ([]*models.User, error) {
f := logrus.Fields{
Expand Down
4 changes: 2 additions & 2 deletions cla-backend-legacy/internal/api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2338,7 +2338,7 @@ func (h *Handlers) InviteCompanyAdminV2(w http.ResponseWriter, r *http.Request)
"project_name": &types.AttributeValueMemberS{Value: projectName},
"user_github_id": &types.AttributeValueMemberS{Value: contributorID},
"user_github_username": &types.AttributeValueMemberS{Value: contributorName},
"user_emails": &types.AttributeValueMemberSS{Value: []string{contributorEmail}},
"user_emails": &types.AttributeValueMemberSS{Value: []string{strings.ToLower(strings.TrimSpace(contributorEmail))}},
"request_status": &types.AttributeValueMemberS{Value: "pending"},
"date_created": &types.AttributeValueMemberS{Value: now},
"date_modified": &types.AttributeValueMemberS{Value: now},
Expand Down Expand Up @@ -2525,7 +2525,7 @@ func (h *Handlers) RequestCompanyCclaV2(w http.ResponseWriter, r *http.Request)
"request_id": &types.AttributeValueMemberS{Value: reqID},
"company_name": &types.AttributeValueMemberS{Value: companyName},
"project_name": &types.AttributeValueMemberS{Value: projectName},
"user_emails": &types.AttributeValueMemberSS{Value: []string{userEmail}},
"user_emails": &types.AttributeValueMemberSS{Value: []string{strings.ToLower(strings.TrimSpace(userEmail))}},
"request_status": &types.AttributeValueMemberS{Value: "pending"},
"date_created": &types.AttributeValueMemberS{Value: now},
"date_modified": &types.AttributeValueMemberS{Value: now},
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/cypress/e2e/v4/cla-manager.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ https://api-gw.dev.platform.linuxfoundation.org/acs/v1/api-docs#tag/Role/operati
method: 'POST',
url: url,
timeout: timeout,
failOnStatusCode: allowFail,
failOnStatusCode: false,
headers: getXACLHeader(),
auth: {
bearer: bearerToken,
Expand Down
28 changes: 28 additions & 0 deletions utils/downcase_emails.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail

STAGE=${STAGE:-dev}
PROFILE="lfproduct-${STAGE}"
REGION=us-east-1
TABLE="cla-${STAGE}-users"
APPLY="${APPLY:-0}"

aws dynamodb scan --profile "$PROFILE" --region "$REGION" --table-name "$TABLE" --projection-expression 'user_id, user_emails' --filter-expression 'attribute_exists(user_emails)' --output json > "${STAGE}_user_emails.json"
cat "${STAGE}_user_emails.json" | jq -c '.Items[] | select(.user_emails.SS != null) | ([.user_emails.SS[] | ascii_downcase | gsub("^\\s+|\\s+$";"") | select(length > 0)] | unique) as $n | select(($n | length > 0) and ($n != (.user_emails.SS | sort)))' \
| while IFS= read -r item; do
Comment thread
lukaszgryglicki marked this conversation as resolved.
uid=$(jq -r '.user_id.S' <<<"$item")
newss=$(jq -c '[.user_emails.SS[] | ascii_downcase | gsub("^\\s+|\\s+$";"") | select(length > 0)] | unique' <<<"$item") # lower + trim + drop-empty + dedupe
if [ "$newss" = "[]" ]
then
echo "skip $uid (no valid emails after normalize)" >&2
continue
fi
echo "user $uid -> $newss"
if [ "$APPLY" = "1" ]
then
aws dynamodb update-item --profile "$PROFILE" --region "$REGION" --table-name "$TABLE" \
--key "{\"user_id\":{\"S\":\"$uid\"}}" \
--update-expression 'SET user_emails = :e' \
--expression-attribute-values "{\":e\":{\"SS\":$newss}}" && echo "ok"
fi
done
Loading