]> source.dussan.org Git - gitea.git/commitdiff
Allow blocking some email domains from registering an account (#14667)
authorDamien Goutte-Gattat <53821801+gouttegd@users.noreply.github.com>
Sun, 14 Feb 2021 23:31:29 +0000 (23:31 +0000)
committerGitHub <noreply@github.com>
Sun, 14 Feb 2021 23:31:29 +0000 (00:31 +0100)
Gitea allows to whitelist email domains so that only email addresses from certain domains are allowed to register an account, but does not currently allows to do the opposite: blacklisting email domains so that addresses from certain domains are *forbidden* to register an account.

The idea has been briefly mentioned in the discussion about issue #6350, but never implemented. This PR does that.

The rationale is that, in my experience of running a Gitea instance, *a single email domain* is responsible for *most* of the spam accounts, and for *all* of the spam accounts that manage to get past the email confirmation step. So on top of the other spam mitigation measures already available (email confirmation, CAPTCHA, etc.), having the option to block a particularly annoying domain would be helpful.

close #13628

custom/conf/app.example.ini
docs/content/doc/advanced/config-cheat-sheet.en-us.md
docs/content/doc/help/faq.en-us.md
modules/forms/user_form.go
modules/forms/user_form_test.go
modules/setting/service.go
routers/user/auth.go

index 276b3cb5e809708f912f0bed6dc0ccfac272fbfb..a6965c2cf642b0bc977fe210f59b2f48dd6e95e8 100644 (file)
@@ -617,6 +617,8 @@ REGISTER_MANUAL_CONFIRM = false
 ; List of domain names that are allowed to be used to register on a Gitea instance
 ; gitea.io,example.com
 EMAIL_DOMAIN_WHITELIST =
+; Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance
+EMAIL_DOMAIN_BLOCKLIST =
 ; Disallow registration, only allow admins to create accounts.
 DISABLE_REGISTRATION = false
 ; Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false
index 2c4d01102c9c165310385bd1a2dc51e8eace3ab6..a7f931b047a9b2f07c30ad3725120683fb6ea4eb 100644 (file)
@@ -466,6 +466,7 @@ relation to port exhaustion.
 - `DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME`: **true**: Only allow users with write permissions to track time.
 - `EMAIL_DOMAIN_WHITELIST`: **\<empty\>**: If non-empty, list of domain names that can only be used to register
   on this instance.
+- `EMAIL_DOMAIN_BLOCKLIST`: **\<empty\>**: If non-empty, list of domain names that cannot be used to register on this instance
 - `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button
 - `SHOW_MILESTONES_DASHBOARD_PAGE`: **true** Enable this to show the milestones dashboard page - a view of all the user's milestones
 - `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created
index 4346c89c38d21127ce0ab302f5979469fbcdf98c..b3cf1aa0e81e540d60c146f8555cd6f8d0934829 100644 (file)
@@ -120,13 +120,14 @@ For more information, refer to Gitea's [API docs]({{< relref "doc/developers/api
 
 There are multiple things you can combine to prevent spammers.
 
-1. By only whitelisting certain domains with OpenID (see below)
-2. Setting `ENABLE_CAPTCHA` to `true` in your `app.ini` and properly configuring `RECAPTCHA_SECRET` and `RECAPTCHA_SITEKEY`
-3. Settings `DISABLE_REGISTRATION` to `true` and creating new users via the [CLI]({{< relref "doc/usage/command-line.en-us.md" >}}), [API]({{< relref "doc/developers/api-usage.en-us.md" >}}), or Gitea's Admin UI
+1. By whitelisting or blocklisting certain email domains
+2. By only whitelisting certain domains with OpenID (see below)
+3. Setting `ENABLE_CAPTCHA` to `true` in your `app.ini` and properly configuring `RECAPTCHA_SECRET` and `RECAPTCHA_SITEKEY`
+4. Settings `DISABLE_REGISTRATION` to `true` and creating new users via the [CLI]({{< relref "doc/usage/command-line.en-us.md" >}}), [API]({{< relref "doc/developers/api-usage.en-us.md" >}}), or Gitea's Admin UI
 
-### Only allow certain email domains
+### Only allow/block certain email domains
 
-You can configure `EMAIL_DOMAIN_WHITELIST` in your app.ini under `[service]`
+You can configure `EMAIL_DOMAIN_WHITELIST` or `EMAIL_DOMAIN_BLOCKLIST` in your app.ini under `[service]`
 
 ### Only allow/block certain OpenID providers
 
index af36628c30fda4e85b0583e43a958e4ab07181f0..07733baebaca14a22e4d8548242dbfb141d305f7 100644 (file)
@@ -95,23 +95,21 @@ func (f *RegisterForm) Validate(req *http.Request, errs binding.Errors) binding.
        return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
 }
 
-// IsEmailDomainWhitelisted validates that the email address
-// provided by the user matches what has been configured .
-// If the domain whitelist from the config is empty, it marks the
-// email as whitelisted
-func (f RegisterForm) IsEmailDomainWhitelisted() bool {
-       if len(setting.Service.EmailDomainWhitelist) == 0 {
-               return true
+// IsEmailDomainListed checks whether the domain of an email address
+// matches a list of domains
+func IsEmailDomainListed(list []string, email string) bool {
+       if len(list) == 0 {
+               return false
        }
 
-       n := strings.LastIndex(f.Email, "@")
+       n := strings.LastIndex(email, "@")
        if n <= 0 {
                return false
        }
 
-       domain := strings.ToLower(f.Email[n+1:])
+       domain := strings.ToLower(email[n+1:])
 
-       for _, v := range setting.Service.EmailDomainWhitelist {
+       for _, v := range list {
                if strings.ToLower(v) == domain {
                        return true
                }
@@ -120,6 +118,19 @@ func (f RegisterForm) IsEmailDomainWhitelisted() bool {
        return false
 }
 
+// IsEmailDomainAllowed validates that the email address
+// provided by the user matches what has been configured .
+// The email is marked as allowed if it matches any of the
+// domains in the whitelist or if it doesn't match any of
+// domains in the blocklist, if any such list is not empty.
+func (f RegisterForm) IsEmailDomainAllowed() bool {
+       if len(setting.Service.EmailDomainWhitelist) == 0 {
+               return !IsEmailDomainListed(setting.Service.EmailDomainBlocklist, f.Email)
+       }
+
+       return IsEmailDomainListed(setting.Service.EmailDomainWhitelist, f.Email)
+}
+
 // MustChangePasswordForm form for updating your password after account creation
 // by an admin
 type MustChangePasswordForm struct {
index 6e0518789c14e63b46a5c5c7cd6e2512871a3555..9f67143d12c818004839b119bc20d8333146ecf7 100644 (file)
@@ -12,17 +12,17 @@ import (
        "github.com/stretchr/testify/assert"
 )
 
-func TestRegisterForm_IsDomainWhiteList_Empty(t *testing.T) {
+func TestRegisterForm_IsDomainAllowed_Empty(t *testing.T) {
        _ = setting.Service
 
        setting.Service.EmailDomainWhitelist = []string{}
 
        form := RegisterForm{}
 
-       assert.True(t, form.IsEmailDomainWhitelisted())
+       assert.True(t, form.IsEmailDomainAllowed())
 }
 
-func TestRegisterForm_IsDomainWhiteList_InvalidEmail(t *testing.T) {
+func TestRegisterForm_IsDomainAllowed_InvalidEmail(t *testing.T) {
        _ = setting.Service
 
        setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
@@ -37,11 +37,11 @@ func TestRegisterForm_IsDomainWhiteList_InvalidEmail(t *testing.T) {
        for _, v := range tt {
                form := RegisterForm{Email: v.email}
 
-               assert.False(t, form.IsEmailDomainWhitelisted())
+               assert.False(t, form.IsEmailDomainAllowed())
        }
 }
 
-func TestRegisterForm_IsDomainWhiteList_ValidEmail(t *testing.T) {
+func TestRegisterForm_IsDomainAllowed_WhitelistedEmail(t *testing.T) {
        _ = setting.Service
 
        setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
@@ -59,6 +59,28 @@ func TestRegisterForm_IsDomainWhiteList_ValidEmail(t *testing.T) {
        for _, v := range tt {
                form := RegisterForm{Email: v.email}
 
-               assert.Equal(t, v.valid, form.IsEmailDomainWhitelisted())
+               assert.Equal(t, v.valid, form.IsEmailDomainAllowed())
+       }
+}
+
+func TestRegisterForm_IsDomainAllowed_BlocklistedEmail(t *testing.T) {
+       _ = setting.Service
+
+       setting.Service.EmailDomainWhitelist = []string{}
+       setting.Service.EmailDomainBlocklist = []string{"gitea.io"}
+
+       tt := []struct {
+               email string
+               valid bool
+       }{
+               {"security@gitea.io", false},
+               {"security@gitea.example", true},
+               {"hdudhdd", true},
+       }
+
+       for _, v := range tt {
+               form := RegisterForm{Email: v.email}
+
+               assert.Equal(t, v.valid, form.IsEmailDomainAllowed())
        }
 }
index b6611830be191257863c703515e2d3bd41069e22..fc4326fde55437d85a0f24ee3a400dfd0890c5d6 100644 (file)
@@ -20,6 +20,7 @@ var Service struct {
        RegisterEmailConfirm                    bool
        RegisterManualConfirm                   bool
        EmailDomainWhitelist                    []string
+       EmailDomainBlocklist                    []string
        DisableRegistration                     bool
        AllowOnlyExternalRegistration           bool
        ShowRegistrationButton                  bool
@@ -72,6 +73,7 @@ func newService() {
                Service.RegisterManualConfirm = false
        }
        Service.EmailDomainWhitelist = sec.Key("EMAIL_DOMAIN_WHITELIST").Strings(",")
+       Service.EmailDomainBlocklist = sec.Key("EMAIL_DOMAIN_BLOCKLIST").Strings(",")
        Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration))
        Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true)
        Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
index bb877767aef8acfd9b0b9becb4c80480992f8ce5..de74055d565f55cf9aed7c7468f1f107c0f7b57b 100644 (file)
@@ -1129,7 +1129,7 @@ func SignUpPost(ctx *context.Context) {
                }
        }
 
-       if !form.IsEmailDomainWhitelisted() {
+       if !form.IsEmailDomainAllowed() {
                ctx.RenderWithErr(ctx.Tr("auth.email_domain_blacklisted"), tplSignUp, &form)
                return
        }