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:
parent
19993d8814
commit
2cb66fff60
|
@ -700,11 +700,11 @@ LEVEL = Info
|
||||||
;; Whether a new user needs to be confirmed manually after registration. (Requires `REGISTER_EMAIL_CONFIRM` to be disabled.)
|
;; Whether a new user needs to be confirmed manually after registration. (Requires `REGISTER_EMAIL_CONFIRM` to be disabled.)
|
||||||
;REGISTER_MANUAL_CONFIRM = false
|
;REGISTER_MANUAL_CONFIRM = false
|
||||||
;;
|
;;
|
||||||
;; List of domain names that are allowed to be used to register on a Gitea instance
|
;; List of domain names that are allowed to be used to register on a Gitea instance, wildcard is supported
|
||||||
;; gitea.io,example.com
|
;; eg: gitea.io,example.com,*.mydomain.com
|
||||||
;EMAIL_DOMAIN_WHITELIST =
|
;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 =
|
;EMAIL_DOMAIN_BLOCKLIST =
|
||||||
;;
|
;;
|
||||||
;; Disallow registration, only allow admins to create accounts.
|
;; Disallow registration, only allow admins to create accounts.
|
||||||
|
|
|
@ -651,9 +651,8 @@ And the following unique queues:
|
||||||
- `ENABLE_TIMETRACKING`: **true**: Enable Timetracking feature.
|
- `ENABLE_TIMETRACKING`: **true**: Enable Timetracking feature.
|
||||||
- `DEFAULT_ENABLE_TIMETRACKING`: **true**: Allow repositories to use timetracking by default.
|
- `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.
|
- `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
|
- `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.
|
||||||
on this instance.
|
- `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.
|
||||||
- `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_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
|
- `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
|
- `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/log"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
|
"github.com/gobwas/glob"
|
||||||
)
|
)
|
||||||
|
|
||||||
// enumerates all the types of captchas
|
// enumerates all the types of captchas
|
||||||
|
@ -33,8 +35,8 @@ var Service = struct {
|
||||||
ResetPwdCodeLives int
|
ResetPwdCodeLives int
|
||||||
RegisterEmailConfirm bool
|
RegisterEmailConfirm bool
|
||||||
RegisterManualConfirm bool
|
RegisterManualConfirm bool
|
||||||
EmailDomainWhitelist []string
|
EmailDomainAllowList []glob.Glob
|
||||||
EmailDomainBlocklist []string
|
EmailDomainBlockList []glob.Glob
|
||||||
DisableRegistration bool
|
DisableRegistration bool
|
||||||
AllowOnlyInternalRegistration bool
|
AllowOnlyInternalRegistration bool
|
||||||
AllowOnlyExternalRegistration bool
|
AllowOnlyExternalRegistration bool
|
||||||
|
@ -114,6 +116,20 @@ func (a AllowedVisibility) ToVisibleTypeSlice() (result []structs.VisibleType) {
|
||||||
return result
|
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) {
|
func loadServiceFrom(rootCfg ConfigProvider) {
|
||||||
sec := rootCfg.Section("service")
|
sec := rootCfg.Section("service")
|
||||||
Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180)
|
Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180)
|
||||||
|
@ -130,8 +146,11 @@ func loadServiceFrom(rootCfg ConfigProvider) {
|
||||||
} else {
|
} else {
|
||||||
Service.RegisterManualConfirm = false
|
Service.RegisterManualConfirm = false
|
||||||
}
|
}
|
||||||
Service.EmailDomainWhitelist = sec.Key("EMAIL_DOMAIN_WHITELIST").Strings(",")
|
if sec.HasKey("EMAIL_DOMAIN_WHITELIST") {
|
||||||
Service.EmailDomainBlocklist = sec.Key("EMAIL_DOMAIN_BLOCKLIST").Strings(",")
|
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.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration))
|
||||||
Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true)
|
Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true)
|
||||||
Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
|
Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
|
||||||
|
|
|
@ -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"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
|
||||||
"gitea.com/go-chi/binding"
|
"gitea.com/go-chi/binding"
|
||||||
|
"github.com/gobwas/glob"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InstallForm form for installation page
|
// 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
|
// IsEmailDomainListed checks whether the domain of an email address
|
||||||
// matches a list of domains
|
// matches a list of domains
|
||||||
func IsEmailDomainListed(list []string, email string) bool {
|
func IsEmailDomainListed(globs []glob.Glob, email string) bool {
|
||||||
if len(list) == 0 {
|
if len(globs) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,8 +118,8 @@ func IsEmailDomainListed(list []string, email string) bool {
|
||||||
|
|
||||||
domain := strings.ToLower(email[n+1:])
|
domain := strings.ToLower(email[n+1:])
|
||||||
|
|
||||||
for _, v := range list {
|
for _, g := range globs {
|
||||||
if strings.ToLower(v) == domain {
|
if g.Match(domain) {
|
||||||
return true
|
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
|
// 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 whitelist or if it doesn't match any of
|
||||||
// domains in the blocklist, if any such list is not empty.
|
// domains in the blocklist, if any such list is not empty.
|
||||||
func (f RegisterForm) IsEmailDomainAllowed() bool {
|
func (f *RegisterForm) IsEmailDomainAllowed() bool {
|
||||||
if len(setting.Service.EmailDomainWhitelist) == 0 {
|
if len(setting.Service.EmailDomainAllowList) == 0 {
|
||||||
return !IsEmailDomainListed(setting.Service.EmailDomainBlocklist, f.Email)
|
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
|
// MustChangePasswordForm form for updating your password after account creation
|
||||||
|
|
|
@ -10,13 +10,17 @@ import (
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
|
"github.com/gobwas/glob"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRegisterForm_IsDomainAllowed_Empty(t *testing.T) {
|
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{}
|
form := RegisterForm{}
|
||||||
|
|
||||||
|
@ -24,15 +28,18 @@ func TestRegisterForm_IsDomainAllowed_Empty(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegisterForm_IsDomainAllowed_InvalidEmail(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 {
|
tt := []struct {
|
||||||
email string
|
email string
|
||||||
}{
|
}{
|
||||||
{"securitygieqqq"},
|
{"invalid-email"},
|
||||||
{"hdudhdd"},
|
{"gitea.io"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range tt {
|
for _, v := range tt {
|
||||||
|
@ -42,10 +49,13 @@ func TestRegisterForm_IsDomainAllowed_InvalidEmail(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegisterForm_IsDomainAllowed_WhitelistedEmail(t *testing.T) {
|
func TestRegisterForm_IsDomainAllowed_AllowedEmail(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"), glob.MustCompile("*.allow")}
|
||||||
|
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
email string
|
email string
|
||||||
|
@ -53,8 +63,11 @@ func TestRegisterForm_IsDomainAllowed_WhitelistedEmail(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{"security@gitea.io", true},
|
{"security@gitea.io", true},
|
||||||
{"security@gITea.io", true},
|
{"security@gITea.io", true},
|
||||||
{"hdudhdd", false},
|
{"invalid", false},
|
||||||
{"seee@example.com", false},
|
{"seee@example.com", false},
|
||||||
|
|
||||||
|
{"user@my.allow", true},
|
||||||
|
{"user@my.allow1", false},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range tt {
|
for _, v := range tt {
|
||||||
|
@ -64,11 +77,14 @@ func TestRegisterForm_IsDomainAllowed_WhitelistedEmail(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegisterForm_IsDomainAllowed_BlocklistedEmail(t *testing.T) {
|
func TestRegisterForm_IsDomainAllowed_BlockedEmail(t *testing.T) {
|
||||||
_ = setting.Service
|
oldService := setting.Service
|
||||||
|
defer func() {
|
||||||
|
setting.Service = oldService
|
||||||
|
}()
|
||||||
|
|
||||||
setting.Service.EmailDomainWhitelist = []string{}
|
setting.Service.EmailDomainAllowList = nil
|
||||||
setting.Service.EmailDomainBlocklist = []string{"gitea.io"}
|
setting.Service.EmailDomainBlockList = []glob.Glob{glob.MustCompile("gitea.io"), glob.MustCompile("*.block")}
|
||||||
|
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
email string
|
email string
|
||||||
|
@ -76,7 +92,10 @@ func TestRegisterForm_IsDomainAllowed_BlocklistedEmail(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{"security@gitea.io", false},
|
{"security@gitea.io", false},
|
||||||
{"security@gitea.example", true},
|
{"security@gitea.example", true},
|
||||||
{"hdudhdd", true},
|
{"invalid", true},
|
||||||
|
|
||||||
|
{"user@my.block", false},
|
||||||
|
{"user@my.block1", true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range tt {
|
for _, v := range tt {
|
||||||
|
|
Loading…
Reference in New Issue