Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support wildcard in email domain allow/block list #24831

Merged
merged 3 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 2 additions & 3 deletions docs/content/doc/administration/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 23 additions & 4 deletions modules/setting/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand Down
46 changes: 46 additions & 0 deletions modules/setting/service_test.go
Original file line number Diff line number Diff line change
@@ -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"))
}
17 changes: 9 additions & 8 deletions services/forms/user_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand All @@ -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
}
}
Expand All @@ -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
Expand Down
49 changes: 34 additions & 15 deletions services/forms/user_form_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,36 @@ 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{}

assert.True(t, form.IsEmailDomainAllowed())
}

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 {
Expand All @@ -42,19 +49,25 @@ 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
valid bool
}{
{"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 {
Expand All @@ -64,19 +77,25 @@ 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
valid bool
}{
{"security@gitea.io", false},
{"security@gitea.example", true},
{"hdudhdd", true},
{"invalid", true},

{"user@my.block", false},
{"user@my.block1", true},
}

for _, v := range tt {
Expand Down