Add a limit for the number of duplicate detumbed emails
What does this MR do and why?
Related to https://gitlab.com/gitlab-org/modelops/anti-abuse/team-tasks/-/issues/812
Add a limit for the number of duplicate detumbled emails that can exist for a user's primary email address. Email providers offer alternative addressing schemes that allow different email addresses to be routed to the same inbox. For example, user+one@ex.com
and user+two@ex.com
will ultimately be routed to the same email inbox. The detumbled version of both email addresses would be user@ex.com
. In order to prevent abusers from creating a large number of accounts with a single email address, this MR will introduce a limit to the number of primary email addresses for users that share the same detumbled address. This limit will not be applied when running in Saas if the email is from gitlab.com
or a verified domain associated with a paid group.
Query Plans
Added users_by_detumbled_email_count scope to emails table
Link: https://console.postgres.ai/gitlab/gitlab-production-main/sessions/32308/commands/99808
Aggregate (cost=3.58..3.59 rows=1 width=8) (actual time=18.827..18.829 rows=1 loops=1)
Buffers: shared hit=9 read=7
I/O Timings: read=18.544 write=0.000
-> Index Scan using index_emails_on_detumbled_email on public.emails (cost=0.56..3.58 rows=1 width=4) (actual time=14.140..18.667 rows=3 loops=1)
Index Cond: (emails.detumbled_email = 'ianderson@gitlab.com'::text)
Buffers: shared read=7
I/O Timings: read=18.544 write=0.000
explain SELECT COUNT(DISTINCT "emails"."user_id") FROM "emails" WHERE "emails"."detumbled_email" = 'user@gitlab.com'
Paid verified domains queries
A query was added to check if a domain name is associated with a paid root group. This method is implemented similar to the worker that associates enterprise users.
root_group = ::PagesDomain.verified.find_by_domain_case_insensitive(email_domain)&.root_group
!!root_group&.paid?
Method | Query Plan Links |
---|---|
::PagesDomain.verified.find_by_domain_case_insensitive(email_domain) |
https://console.postgres.ai/gitlab/gitlab-production-main/sessions/32308/commands/99818 |
root_group |
https://console.postgres.ai/gitlab/gitlab-production-main/sessions/32308/commands/99819 https://console.postgres.ai/gitlab/gitlab-production-main/sessions/32308/commands/99820 |
root_group.paid? |
https://console.postgres.ai/gitlab/gitlab-production-main/sessions/32308/commands/99821 https://console.postgres.ai/gitlab/gitlab-production-main/sessions/32308/commands/99822 |
::PagesDomain.verified.find_by_domain_case_insensitive(email_domain)
MR acceptance checklist
Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Screenshots or screen recordings
Screenshots are required for UI changes, and strongly recommended for all other merge requests.
Before | After |
---|---|
How to set up and validate locally
Numbered steps to set up and validate the change are strongly suggested.
- Enable the feature
Feature.enable(:limit_normalized_email_reuse)
- Apply the patch to set the reuse limit to 1 normalized email.
diff --git a/app/validators/anti_abuse/unique_detumbled_email_validator.rb b/app/validators/anti_abuse/unique_detumbled_email_validator.rb index d65357980088..2994958cbb5a 100644 --- a/app/validators/anti_abuse/unique_detumbled_email_validator.rb +++ b/app/validators/anti_abuse/unique_detumbled_email_validator.rb @@ -2,7 +2,7 @@ module AntiAbuse class UniqueDetumbledEmailValidator < ActiveModel::Validator - NORMALIZED_EMAIL_ACCOUNT_LIMIT = 5 + NORMALIZED_EMAIL_ACCOUNT_LIMIT = 1 def validate(record) return if record.errors.include?(:email)
- Register a new user with the same detumbled email as another user. For example, if the user is registered with
user@ex.com
then attempt to register a new user with an email ofuser+test@ex.com
. You should see an error that the email is not allowed when you try to register the user. - Ensure GDK is running in SAAS mode by setting
GITLAB_SIMULATE_SAAS=1
. - Setup a verified domain and attempt to the tests before verifying the domain. The registration should error when the limit is reached.
- Verify the domain
PagesDomain.last.update!(verified_at: Time.now)
- Re-attempt the user registration. The user should not be allowed to register.