Skip to content
Merged
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
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