Skip to content

Commit 2cb66ff

Browse files
authored
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
1 parent 19993d8 commit 2cb66ff

File tree

6 files changed

+118
-34
lines changed

6 files changed

+118
-34
lines changed

custom/conf/app.example.ini

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -700,11 +700,11 @@ LEVEL = Info
700700
;; Whether a new user needs to be confirmed manually after registration. (Requires `REGISTER_EMAIL_CONFIRM` to be disabled.)
701701
;REGISTER_MANUAL_CONFIRM = false
702702
;;
703-
;; List of domain names that are allowed to be used to register on a Gitea instance
704-
;; gitea.io,example.com
705-
;EMAIL_DOMAIN_WHITELIST =
703+
;; List of domain names that are allowed to be used to register on a Gitea instance, wildcard is supported
704+
;; eg: gitea.io,example.com,*.mydomain.com
705+
;EMAIL_DOMAIN_ALLOWLIST =
706706
;;
707-
;; Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance
707+
;; Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance, wildcard is supported
708708
;EMAIL_DOMAIN_BLOCKLIST =
709709
;;
710710
;; Disallow registration, only allow admins to create accounts.

docs/content/doc/administration/config-cheat-sheet.en-us.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -651,9 +651,8 @@ And the following unique queues:
651651
- `ENABLE_TIMETRACKING`: **true**: Enable Timetracking feature.
652652
- `DEFAULT_ENABLE_TIMETRACKING`: **true**: Allow repositories to use timetracking by default.
653653
- `DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME`: **true**: Only allow users with write permissions to track time.
654-
- `EMAIL_DOMAIN_WHITELIST`: **\<empty\>**: If non-empty, list of domain names that can only be used to register
655-
on this instance.
656-
- `EMAIL_DOMAIN_BLOCKLIST`: **\<empty\>**: If non-empty, list of domain names that cannot be used to register on this instance
654+
- `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.
655+
- `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.
657656
- `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button
658657
- `SHOW_MILESTONES_DASHBOARD_PAGE`: **true** Enable this to show the milestones dashboard page - a view of all the user's milestones
659658
- `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created

modules/setting/service.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010

1111
"code.gitea.io/gitea/modules/log"
1212
"code.gitea.io/gitea/modules/structs"
13+
14+
"github.com/gobwas/glob"
1315
)
1416

1517
// enumerates all the types of captchas
@@ -33,8 +35,8 @@ var Service = struct {
3335
ResetPwdCodeLives int
3436
RegisterEmailConfirm bool
3537
RegisterManualConfirm bool
36-
EmailDomainWhitelist []string
37-
EmailDomainBlocklist []string
38+
EmailDomainAllowList []glob.Glob
39+
EmailDomainBlockList []glob.Glob
3840
DisableRegistration bool
3941
AllowOnlyInternalRegistration bool
4042
AllowOnlyExternalRegistration bool
@@ -114,6 +116,20 @@ func (a AllowedVisibility) ToVisibleTypeSlice() (result []structs.VisibleType) {
114116
return result
115117
}
116118

119+
func CompileEmailGlobList(sec ConfigSection, keys ...string) (globs []glob.Glob) {
120+
for _, key := range keys {
121+
list := sec.Key(key).Strings(",")
122+
for _, s := range list {
123+
if g, err := glob.Compile(s); err == nil {
124+
globs = append(globs, g)
125+
} else {
126+
log.Error("Skip invalid email allow/block list expression %q: %v", s, err)
127+
}
128+
}
129+
}
130+
return globs
131+
}
132+
117133
func loadServiceFrom(rootCfg ConfigProvider) {
118134
sec := rootCfg.Section("service")
119135
Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180)
@@ -130,8 +146,11 @@ func loadServiceFrom(rootCfg ConfigProvider) {
130146
} else {
131147
Service.RegisterManualConfirm = false
132148
}
133-
Service.EmailDomainWhitelist = sec.Key("EMAIL_DOMAIN_WHITELIST").Strings(",")
134-
Service.EmailDomainBlocklist = sec.Key("EMAIL_DOMAIN_BLOCKLIST").Strings(",")
149+
if sec.HasKey("EMAIL_DOMAIN_WHITELIST") {
150+
deprecatedSetting(rootCfg, "service", "EMAIL_DOMAIN_WHITELIST", "service", "EMAIL_DOMAIN_ALLOWLIST", "1.21")
151+
}
152+
Service.EmailDomainAllowList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_WHITELIST", "EMAIL_DOMAIN_ALLOWLIST")
153+
Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST")
135154
Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration))
136155
Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true)
137156
Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()

modules/setting/service_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package setting
5+
6+
import (
7+
"testing"
8+
9+
"github.com/gobwas/glob"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestLoadServices(t *testing.T) {
14+
oldService := Service
15+
defer func() {
16+
Service = oldService
17+
}()
18+
19+
cfg, err := NewConfigProviderFromData(`
20+
[service]
21+
EMAIL_DOMAIN_WHITELIST = d1, *.w
22+
EMAIL_DOMAIN_ALLOWLIST = d2, *.a
23+
EMAIL_DOMAIN_BLOCKLIST = d3, *.b
24+
`)
25+
assert.NoError(t, err)
26+
loadServiceFrom(cfg)
27+
28+
match := func(globs []glob.Glob, s string) bool {
29+
for _, g := range globs {
30+
if g.Match(s) {
31+
return true
32+
}
33+
}
34+
return false
35+
}
36+
37+
assert.True(t, match(Service.EmailDomainAllowList, "d1"))
38+
assert.True(t, match(Service.EmailDomainAllowList, "foo.w"))
39+
assert.True(t, match(Service.EmailDomainAllowList, "d2"))
40+
assert.True(t, match(Service.EmailDomainAllowList, "foo.a"))
41+
assert.False(t, match(Service.EmailDomainAllowList, "d3"))
42+
43+
assert.True(t, match(Service.EmailDomainBlockList, "d3"))
44+
assert.True(t, match(Service.EmailDomainBlockList, "foo.b"))
45+
assert.False(t, match(Service.EmailDomainBlockList, "d1"))
46+
}

services/forms/user_form.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"code.gitea.io/gitea/modules/web/middleware"
1717

1818
"gitea.com/go-chi/binding"
19+
"github.com/gobwas/glob"
1920
)
2021

2122
// InstallForm form for installation page
@@ -105,8 +106,8 @@ func (f *RegisterForm) Validate(req *http.Request, errs binding.Errors) binding.
105106

106107
// IsEmailDomainListed checks whether the domain of an email address
107108
// matches a list of domains
108-
func IsEmailDomainListed(list []string, email string) bool {
109-
if len(list) == 0 {
109+
func IsEmailDomainListed(globs []glob.Glob, email string) bool {
110+
if len(globs) == 0 {
110111
return false
111112
}
112113

@@ -117,8 +118,8 @@ func IsEmailDomainListed(list []string, email string) bool {
117118

118119
domain := strings.ToLower(email[n+1:])
119120

120-
for _, v := range list {
121-
if strings.ToLower(v) == domain {
121+
for _, g := range globs {
122+
if g.Match(domain) {
122123
return true
123124
}
124125
}
@@ -131,12 +132,12 @@ func IsEmailDomainListed(list []string, email string) bool {
131132
// The email is marked as allowed if it matches any of the
132133
// domains in the whitelist or if it doesn't match any of
133134
// domains in the blocklist, if any such list is not empty.
134-
func (f RegisterForm) IsEmailDomainAllowed() bool {
135-
if len(setting.Service.EmailDomainWhitelist) == 0 {
136-
return !IsEmailDomainListed(setting.Service.EmailDomainBlocklist, f.Email)
135+
func (f *RegisterForm) IsEmailDomainAllowed() bool {
136+
if len(setting.Service.EmailDomainAllowList) == 0 {
137+
return !IsEmailDomainListed(setting.Service.EmailDomainBlockList, f.Email)
137138
}
138139

139-
return IsEmailDomainListed(setting.Service.EmailDomainWhitelist, f.Email)
140+
return IsEmailDomainListed(setting.Service.EmailDomainAllowList, f.Email)
140141
}
141142

142143
// MustChangePasswordForm form for updating your password after account creation

services/forms/user_form_test.go

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,36 @@ import (
1010
auth_model "code.gitea.io/gitea/models/auth"
1111
"code.gitea.io/gitea/modules/setting"
1212

13+
"github.com/gobwas/glob"
1314
"github.com/stretchr/testify/assert"
1415
)
1516

1617
func TestRegisterForm_IsDomainAllowed_Empty(t *testing.T) {
17-
_ = setting.Service
18+
oldService := setting.Service
19+
defer func() {
20+
setting.Service = oldService
21+
}()
1822

19-
setting.Service.EmailDomainWhitelist = []string{}
23+
setting.Service.EmailDomainAllowList = nil
2024

2125
form := RegisterForm{}
2226

2327
assert.True(t, form.IsEmailDomainAllowed())
2428
}
2529

2630
func TestRegisterForm_IsDomainAllowed_InvalidEmail(t *testing.T) {
27-
_ = setting.Service
31+
oldService := setting.Service
32+
defer func() {
33+
setting.Service = oldService
34+
}()
2835

29-
setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
36+
setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("gitea.io")}
3037

3138
tt := []struct {
3239
email string
3340
}{
34-
{"securitygieqqq"},
35-
{"hdudhdd"},
41+
{"invalid-email"},
42+
{"gitea.io"},
3643
}
3744

3845
for _, v := range tt {
@@ -42,19 +49,25 @@ func TestRegisterForm_IsDomainAllowed_InvalidEmail(t *testing.T) {
4249
}
4350
}
4451

45-
func TestRegisterForm_IsDomainAllowed_WhitelistedEmail(t *testing.T) {
46-
_ = setting.Service
52+
func TestRegisterForm_IsDomainAllowed_AllowedEmail(t *testing.T) {
53+
oldService := setting.Service
54+
defer func() {
55+
setting.Service = oldService
56+
}()
4757

48-
setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
58+
setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("gitea.io"), glob.MustCompile("*.allow")}
4959

5060
tt := []struct {
5161
email string
5262
valid bool
5363
}{
5464
{"[email protected]", true},
5565
{"[email protected]", true},
56-
{"hdudhdd", false},
66+
{"invalid", false},
5767
{"[email protected]", false},
68+
69+
{"[email protected]", true},
70+
{"[email protected]", false},
5871
}
5972

6073
for _, v := range tt {
@@ -64,19 +77,25 @@ func TestRegisterForm_IsDomainAllowed_WhitelistedEmail(t *testing.T) {
6477
}
6578
}
6679

67-
func TestRegisterForm_IsDomainAllowed_BlocklistedEmail(t *testing.T) {
68-
_ = setting.Service
80+
func TestRegisterForm_IsDomainAllowed_BlockedEmail(t *testing.T) {
81+
oldService := setting.Service
82+
defer func() {
83+
setting.Service = oldService
84+
}()
6985

70-
setting.Service.EmailDomainWhitelist = []string{}
71-
setting.Service.EmailDomainBlocklist = []string{"gitea.io"}
86+
setting.Service.EmailDomainAllowList = nil
87+
setting.Service.EmailDomainBlockList = []glob.Glob{glob.MustCompile("gitea.io"), glob.MustCompile("*.block")}
7288

7389
tt := []struct {
7490
email string
7591
valid bool
7692
}{
7793
{"[email protected]", false},
7894
{"[email protected]", true},
79-
{"hdudhdd", true},
95+
{"invalid", true},
96+
97+
{"[email protected]", false},
98+
{"[email protected]", true},
8099
}
81100

82101
for _, v := range tt {

0 commit comments

Comments
 (0)