mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Support wildcard in email domain allow/block list (#24831)
Replace #20257 (which is stale and incomplete) Close #20255 Major changes: * Deprecate the "WHITELIST", use "ALLOWLIST" * Add wildcard support for EMAIL_DOMAIN_ALLOWLIST/EMAIL_DOMAIN_BLOCKLIST * Update example config file and document * Improve tests
This commit is contained in:
		| @@ -700,11 +700,11 @@ LEVEL = Info | ||||
| ;; Whether a new user needs to be confirmed manually after registration. (Requires `REGISTER_EMAIL_CONFIRM` to be disabled.) | ||||
| ;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 = | ||||
| ;; List of domain names that are allowed to be used to register on a Gitea instance, wildcard is supported | ||||
| ;; eg: gitea.io,example.com,*.mydomain.com | ||||
| ;EMAIL_DOMAIN_ALLOWLIST = | ||||
| ;; | ||||
| ;; Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance | ||||
| ;; Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance, wildcard is supported | ||||
| ;EMAIL_DOMAIN_BLOCKLIST = | ||||
| ;; | ||||
| ;; Disallow registration, only allow admins to create accounts. | ||||
|   | ||||
| @@ -651,9 +651,8 @@ And the following unique queues: | ||||
| - `ENABLE_TIMETRACKING`: **true**: Enable Timetracking feature. | ||||
| - `DEFAULT_ENABLE_TIMETRACKING`: **true**: Allow repositories to use timetracking by default. | ||||
| - `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 | ||||
| - `EMAIL_DOMAIN_ALLOWLIST`: **\<empty\>**: If non-empty, comma separated list of domain names that can only be used to register on this instance, wildcard is supported. | ||||
| - `EMAIL_DOMAIN_BLOCKLIST`: **\<empty\>**: If non-empty, comma separated list of domain names that cannot be used to register on this instance, wildcard is supported. | ||||
| - `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 | ||||
|   | ||||
| @@ -10,6 +10,8 @@ import ( | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/structs" | ||||
|  | ||||
| 	"github.com/gobwas/glob" | ||||
| ) | ||||
|  | ||||
| // enumerates all the types of captchas | ||||
| @@ -33,8 +35,8 @@ var Service = struct { | ||||
| 	ResetPwdCodeLives                       int | ||||
| 	RegisterEmailConfirm                    bool | ||||
| 	RegisterManualConfirm                   bool | ||||
| 	EmailDomainWhitelist                    []string | ||||
| 	EmailDomainBlocklist                    []string | ||||
| 	EmailDomainAllowList                    []glob.Glob | ||||
| 	EmailDomainBlockList                    []glob.Glob | ||||
| 	DisableRegistration                     bool | ||||
| 	AllowOnlyInternalRegistration           bool | ||||
| 	AllowOnlyExternalRegistration           bool | ||||
| @@ -114,6 +116,20 @@ func (a AllowedVisibility) ToVisibleTypeSlice() (result []structs.VisibleType) { | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| func CompileEmailGlobList(sec ConfigSection, keys ...string) (globs []glob.Glob) { | ||||
| 	for _, key := range keys { | ||||
| 		list := sec.Key(key).Strings(",") | ||||
| 		for _, s := range list { | ||||
| 			if g, err := glob.Compile(s); err == nil { | ||||
| 				globs = append(globs, g) | ||||
| 			} else { | ||||
| 				log.Error("Skip invalid email allow/block list expression %q: %v", s, err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return globs | ||||
| } | ||||
|  | ||||
| func loadServiceFrom(rootCfg ConfigProvider) { | ||||
| 	sec := rootCfg.Section("service") | ||||
| 	Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180) | ||||
| @@ -130,8 +146,11 @@ func loadServiceFrom(rootCfg ConfigProvider) { | ||||
| 	} else { | ||||
| 		Service.RegisterManualConfirm = false | ||||
| 	} | ||||
| 	Service.EmailDomainWhitelist = sec.Key("EMAIL_DOMAIN_WHITELIST").Strings(",") | ||||
| 	Service.EmailDomainBlocklist = sec.Key("EMAIL_DOMAIN_BLOCKLIST").Strings(",") | ||||
| 	if sec.HasKey("EMAIL_DOMAIN_WHITELIST") { | ||||
| 		deprecatedSetting(rootCfg, "service", "EMAIL_DOMAIN_WHITELIST", "service", "EMAIL_DOMAIN_ALLOWLIST", "1.21") | ||||
| 	} | ||||
| 	Service.EmailDomainAllowList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_WHITELIST", "EMAIL_DOMAIN_ALLOWLIST") | ||||
| 	Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST") | ||||
| 	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() | ||||
|   | ||||
							
								
								
									
										46
									
								
								modules/setting/service_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								modules/setting/service_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| // Copyright 2023 The Gitea Authors. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
|  | ||||
| package setting | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/gobwas/glob" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestLoadServices(t *testing.T) { | ||||
| 	oldService := Service | ||||
| 	defer func() { | ||||
| 		Service = oldService | ||||
| 	}() | ||||
|  | ||||
| 	cfg, err := NewConfigProviderFromData(` | ||||
| [service] | ||||
| EMAIL_DOMAIN_WHITELIST = d1, *.w | ||||
| EMAIL_DOMAIN_ALLOWLIST = d2, *.a | ||||
| EMAIL_DOMAIN_BLOCKLIST = d3, *.b | ||||
| `) | ||||
| 	assert.NoError(t, err) | ||||
| 	loadServiceFrom(cfg) | ||||
|  | ||||
| 	match := func(globs []glob.Glob, s string) bool { | ||||
| 		for _, g := range globs { | ||||
| 			if g.Match(s) { | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	assert.True(t, match(Service.EmailDomainAllowList, "d1")) | ||||
| 	assert.True(t, match(Service.EmailDomainAllowList, "foo.w")) | ||||
| 	assert.True(t, match(Service.EmailDomainAllowList, "d2")) | ||||
| 	assert.True(t, match(Service.EmailDomainAllowList, "foo.a")) | ||||
| 	assert.False(t, match(Service.EmailDomainAllowList, "d3")) | ||||
|  | ||||
| 	assert.True(t, match(Service.EmailDomainBlockList, "d3")) | ||||
| 	assert.True(t, match(Service.EmailDomainBlockList, "foo.b")) | ||||
| 	assert.False(t, match(Service.EmailDomainBlockList, "d1")) | ||||
| } | ||||
| @@ -16,6 +16,7 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/web/middleware" | ||||
|  | ||||
| 	"gitea.com/go-chi/binding" | ||||
| 	"github.com/gobwas/glob" | ||||
| ) | ||||
|  | ||||
| // InstallForm form for installation page | ||||
| @@ -105,8 +106,8 @@ func (f *RegisterForm) Validate(req *http.Request, errs binding.Errors) binding. | ||||
|  | ||||
| // 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 { | ||||
| func IsEmailDomainListed(globs []glob.Glob, email string) bool { | ||||
| 	if len(globs) == 0 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| @@ -117,8 +118,8 @@ func IsEmailDomainListed(list []string, email string) bool { | ||||
|  | ||||
| 	domain := strings.ToLower(email[n+1:]) | ||||
|  | ||||
| 	for _, v := range list { | ||||
| 		if strings.ToLower(v) == domain { | ||||
| 	for _, g := range globs { | ||||
| 		if g.Match(domain) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| @@ -131,12 +132,12 @@ func IsEmailDomainListed(list []string, email string) bool { | ||||
| // 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) | ||||
| func (f *RegisterForm) IsEmailDomainAllowed() bool { | ||||
| 	if len(setting.Service.EmailDomainAllowList) == 0 { | ||||
| 		return !IsEmailDomainListed(setting.Service.EmailDomainBlockList, f.Email) | ||||
| 	} | ||||
|  | ||||
| 	return IsEmailDomainListed(setting.Service.EmailDomainWhitelist, f.Email) | ||||
| 	return IsEmailDomainListed(setting.Service.EmailDomainAllowList, f.Email) | ||||
| } | ||||
|  | ||||
| // MustChangePasswordForm form for updating your password after account creation | ||||
|   | ||||
| @@ -10,13 +10,17 @@ import ( | ||||
| 	auth_model "code.gitea.io/gitea/models/auth" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
|  | ||||
| 	"github.com/gobwas/glob" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestRegisterForm_IsDomainAllowed_Empty(t *testing.T) { | ||||
| 	_ = setting.Service | ||||
| 	oldService := setting.Service | ||||
| 	defer func() { | ||||
| 		setting.Service = oldService | ||||
| 	}() | ||||
|  | ||||
| 	setting.Service.EmailDomainWhitelist = []string{} | ||||
| 	setting.Service.EmailDomainAllowList = nil | ||||
|  | ||||
| 	form := RegisterForm{} | ||||
|  | ||||
| @@ -24,15 +28,18 @@ func TestRegisterForm_IsDomainAllowed_Empty(t *testing.T) { | ||||
| } | ||||
|  | ||||
| func TestRegisterForm_IsDomainAllowed_InvalidEmail(t *testing.T) { | ||||
| 	_ = setting.Service | ||||
| 	oldService := setting.Service | ||||
| 	defer func() { | ||||
| 		setting.Service = oldService | ||||
| 	}() | ||||
|  | ||||
| 	setting.Service.EmailDomainWhitelist = []string{"gitea.io"} | ||||
| 	setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("gitea.io")} | ||||
|  | ||||
| 	tt := []struct { | ||||
| 		email string | ||||
| 	}{ | ||||
| 		{"securitygieqqq"}, | ||||
| 		{"hdudhdd"}, | ||||
| 		{"invalid-email"}, | ||||
| 		{"gitea.io"}, | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range tt { | ||||
| @@ -42,10 +49,13 @@ func TestRegisterForm_IsDomainAllowed_InvalidEmail(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRegisterForm_IsDomainAllowed_WhitelistedEmail(t *testing.T) { | ||||
| 	_ = setting.Service | ||||
| func TestRegisterForm_IsDomainAllowed_AllowedEmail(t *testing.T) { | ||||
| 	oldService := setting.Service | ||||
| 	defer func() { | ||||
| 		setting.Service = oldService | ||||
| 	}() | ||||
|  | ||||
| 	setting.Service.EmailDomainWhitelist = []string{"gitea.io"} | ||||
| 	setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("gitea.io"), glob.MustCompile("*.allow")} | ||||
|  | ||||
| 	tt := []struct { | ||||
| 		email string | ||||
| @@ -53,8 +63,11 @@ func TestRegisterForm_IsDomainAllowed_WhitelistedEmail(t *testing.T) { | ||||
| 	}{ | ||||
| 		{"security@gitea.io", true}, | ||||
| 		{"security@gITea.io", true}, | ||||
| 		{"hdudhdd", false}, | ||||
| 		{"invalid", false}, | ||||
| 		{"seee@example.com", false}, | ||||
|  | ||||
| 		{"user@my.allow", true}, | ||||
| 		{"user@my.allow1", false}, | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range tt { | ||||
| @@ -64,11 +77,14 @@ func TestRegisterForm_IsDomainAllowed_WhitelistedEmail(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRegisterForm_IsDomainAllowed_BlocklistedEmail(t *testing.T) { | ||||
| 	_ = setting.Service | ||||
| func TestRegisterForm_IsDomainAllowed_BlockedEmail(t *testing.T) { | ||||
| 	oldService := setting.Service | ||||
| 	defer func() { | ||||
| 		setting.Service = oldService | ||||
| 	}() | ||||
|  | ||||
| 	setting.Service.EmailDomainWhitelist = []string{} | ||||
| 	setting.Service.EmailDomainBlocklist = []string{"gitea.io"} | ||||
| 	setting.Service.EmailDomainAllowList = nil | ||||
| 	setting.Service.EmailDomainBlockList = []glob.Glob{glob.MustCompile("gitea.io"), glob.MustCompile("*.block")} | ||||
|  | ||||
| 	tt := []struct { | ||||
| 		email string | ||||
| @@ -76,7 +92,10 @@ func TestRegisterForm_IsDomainAllowed_BlocklistedEmail(t *testing.T) { | ||||
| 	}{ | ||||
| 		{"security@gitea.io", false}, | ||||
| 		{"security@gitea.example", true}, | ||||
| 		{"hdudhdd", true}, | ||||
| 		{"invalid", true}, | ||||
|  | ||||
| 		{"user@my.block", false}, | ||||
| 		{"user@my.block1", true}, | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range tt { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user