From 9eebadce0205459ba6c4452447f12cf0cd31eb7f Mon Sep 17 00:00:00 2001 From: patdyn Date: Sun, 23 Feb 2025 08:02:10 +0000 Subject: [PATCH] federation with allow lists (#5393) ## Description This addresses Issue #5379. The email validation was extended. Additionally to checking whether the email domain is in the block list or in the allow list now we also check if the email domain is the servers own FQDN. Tests have been written for the correct function of the allow list and if the local FQDN is admitted as email domain. Edit: Clarifications, Typos ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [x] in their respective `*_test.go` for unit tests. ### Documentation - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [x] I want the title to show in the release notes with a link to this pull request. Co-authored-by: Michael Jerger Co-authored-by: patdyn Co-authored-by: Mirco Co-authored-by: jerger Co-authored-by: zam Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5393 Reviewed-by: jerger Reviewed-by: Gusted Co-authored-by: patdyn Co-committed-by: patdyn --- modules/setting/service.go | 26 +++++++++- modules/setting/setting.go | 3 +- modules/setting/setting_test.go | 83 ++++++++++++++++++++++++++++++++ modules/validation/email.go | 23 +++++++-- modules/validation/email_test.go | 6 +++ 5 files changed, 135 insertions(+), 6 deletions(-) diff --git a/modules/setting/service.go b/modules/setting/service.go index 7a907023c4..cc84ac7257 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -1,9 +1,11 @@ // Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved // SPDX-License-Identifier: MIT package setting import ( + "net/url" "regexp" "slices" "strings" @@ -145,6 +147,20 @@ func LoadServiceSetting() { loadServiceFrom(CfgProvider) } +func appURLAsGlob(fqdn string) (glob.Glob, error) { + localFqdn, err := url.ParseRequestURI(fqdn) + if err != nil { + log.Error("Error in EmailDomainAllowList: %v", err) + return nil, err + } + appFqdn, err := glob.Compile(localFqdn.Hostname(), ',') + if err != nil { + log.Error("Error in EmailDomainAllowList: %v", err) + return nil, err + } + return appFqdn, nil +} + func loadServiceFrom(rootCfg ConfigProvider) { sec := rootCfg.Section("service") Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180) @@ -164,7 +180,15 @@ func loadServiceFrom(rootCfg ConfigProvider) { 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") + emailDomainAllowList := CompileEmailGlobList(sec, "EMAIL_DOMAIN_WHITELIST", "EMAIL_DOMAIN_ALLOWLIST") + + if len(emailDomainAllowList) > 0 && Federation.Enabled { + appURL, err := appURLAsGlob(AppURL) + if err == nil { + emailDomainAllowList = append(emailDomainAllowList, appURL) + } + } + Service.EmailDomainAllowList = emailDomainAllowList Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST") Service.EmailDomainBlockDisposable = sec.Key("EMAIL_DOMAIN_BLOCK_DISPOSABLE").MustBool(false) if Service.EmailDomainBlockDisposable { diff --git a/modules/setting/setting.go b/modules/setting/setting.go index c9d30836ac..8350b914c5 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1,5 +1,6 @@ // Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2017 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved // SPDX-License-Identifier: MIT package setting @@ -210,6 +211,7 @@ func LoadSettings() { initAllLoggers() loadDBSetting(CfgProvider) + loadFederationFrom(CfgProvider) loadServiceFrom(CfgProvider) loadOAuth2ClientFrom(CfgProvider) loadCacheFrom(CfgProvider) @@ -224,7 +226,6 @@ func LoadSettings() { LoadQueueSettings() loadProjectFrom(CfgProvider) loadMimeTypeMapFrom(CfgProvider) - loadFederationFrom(CfgProvider) loadF3From(CfgProvider) } diff --git a/modules/setting/setting_test.go b/modules/setting/setting_test.go index f77ee65974..6801844729 100644 --- a/modules/setting/setting_test.go +++ b/modules/setting/setting_test.go @@ -1,4 +1,5 @@ // Copyright 2020 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved // SPDX-License-Identifier: MIT package setting @@ -9,6 +10,7 @@ import ( "code.gitea.io/gitea/modules/json" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMakeAbsoluteAssetURL(t *testing.T) { @@ -30,3 +32,84 @@ func TestMakeManifestData(t *testing.T) { jsonBytes := MakeManifestData(`Example App '\"`, "https://example.com", "https://example.com/foo/bar") assert.True(t, json.Valid(jsonBytes)) } + +func TestLoadServiceDomainListsForFederation(t *testing.T) { + oldAppURL := AppURL + oldFederation := Federation + oldService := Service + + defer func() { + AppURL = oldAppURL + Federation = oldFederation + Service = oldService + }() + + cfg, err := NewConfigProviderFromData(` +[federation] +ENABLED = true +[service] +EMAIL_DOMAIN_ALLOWLIST = *.allow.random +EMAIL_DOMAIN_BLOCKLIST = *.block.random +`) + + require.NoError(t, err) + loadServerFrom(cfg) + loadFederationFrom(cfg) + loadServiceFrom(cfg) + + assert.True(t, match(Service.EmailDomainAllowList, "d1.allow.random")) + assert.True(t, match(Service.EmailDomainAllowList, "localhost")) +} + +func TestLoadServiceDomainListsNoFederation(t *testing.T) { + oldAppURL := AppURL + oldFederation := Federation + oldService := Service + + defer func() { + AppURL = oldAppURL + Federation = oldFederation + Service = oldService + }() + + cfg, err := NewConfigProviderFromData(` +[federation] +ENABLED = false +[service] +EMAIL_DOMAIN_ALLOWLIST = *.allow.random +EMAIL_DOMAIN_BLOCKLIST = *.block.random +`) + + require.NoError(t, err) + loadServerFrom(cfg) + loadFederationFrom(cfg) + loadServiceFrom(cfg) + + assert.True(t, match(Service.EmailDomainAllowList, "d1.allow.random")) +} + +func TestLoadServiceDomainListsFederationEmptyAllowList(t *testing.T) { + oldAppURL := AppURL + oldFederation := Federation + oldService := Service + + defer func() { + AppURL = oldAppURL + Federation = oldFederation + Service = oldService + }() + + cfg, err := NewConfigProviderFromData(` +[federation] +ENABLED = true +[service] +EMAIL_DOMAIN_BLOCKLIST = *.block.random +`) + + require.NoError(t, err) + loadServerFrom(cfg) + loadFederationFrom(cfg) + loadServiceFrom(cfg) + + assert.Empty(t, Service.EmailDomainAllowList) +} diff --git a/modules/validation/email.go b/modules/validation/email.go index bef816586f..326e93378a 100644 --- a/modules/validation/email.go +++ b/modules/validation/email.go @@ -1,5 +1,6 @@ // Copyright 2016 The Gogs Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved. +// Copyright 2024 The Forgejo Authors. All rights reserved // SPDX-License-Identifier: MIT package validation @@ -100,11 +101,25 @@ func validateEmailDomain(email string) error { } func IsEmailDomainAllowed(email string) bool { - if len(setting.Service.EmailDomainAllowList) == 0 { - return !isEmailDomainListed(setting.Service.EmailDomainBlockList, email) - } + return isEmailDomainAllowedInternal( + email, + setting.Service.EmailDomainAllowList, + setting.Service.EmailDomainBlockList) +} - return isEmailDomainListed(setting.Service.EmailDomainAllowList, email) +func isEmailDomainAllowedInternal( + email string, + emailDomainAllowList []glob.Glob, + emailDomainBlockList []glob.Glob, +) bool { + var result bool + + if len(emailDomainAllowList) == 0 { + result = !isEmailDomainListed(emailDomainBlockList, email) + } else { + result = isEmailDomainListed(emailDomainAllowList, email) + } + return result } // isEmailDomainListed checks whether the domain of an email address diff --git a/modules/validation/email_test.go b/modules/validation/email_test.go index e5125a9357..ffdc6fd4ee 100644 --- a/modules/validation/email_test.go +++ b/modules/validation/email_test.go @@ -1,4 +1,5 @@ // Copyright 2017 The Gitea Authors. All rights reserved. +// Copyright 2024 The Forgejo Authors. All rights reserved // SPDX-License-Identifier: MIT package validation @@ -65,3 +66,8 @@ func TestEmailAddressValidate(t *testing.T) { }) } } + +func TestEmailDomainAllowList(t *testing.T) { + res := IsEmailDomainAllowed("someuser@localhost.localdomain") + assert.True(t, res) +}