323 lines · 14.2 KB · Go
RawDownload
// 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"	"time" 	"forgejo.org/modules/log"	"forgejo.org/modules/structs" 	"github.com/gobwas/glob") // enumerates all the types of captchasconst (	ImageCaptcha = "image"	ReCaptcha    = "recaptcha"	HCaptcha     = "hcaptcha"	MCaptcha     = "mcaptcha"	CfTurnstile  = "cfturnstile") // Service settingsvar Service = struct {	DefaultUserVisibility                   string	DefaultUserVisibilityMode               structs.VisibleType	AllowedUserVisibilityModes              []string	AllowedUserVisibilityModesSlice         AllowedVisibility `ini:"-"`	DefaultOrgVisibility                    string	DefaultOrgVisibilityMode                structs.VisibleType	ActiveCodeLives                         int	ResetPwdCodeLives                       int	RegisterEmailConfirm                    bool	RegisterManualConfirm                   bool	EmailDomainAllowList                    []glob.Glob	EmailDomainBlockList                    []glob.Glob	EmailDomainBlockDisposable              bool	DisableRegistration                     bool	AllowOnlyInternalRegistration           bool	AllowOnlyExternalRegistration           bool	ShowRegistrationButton                  bool	EnableInternalSignIn                    bool	ShowMilestonesDashboardPage             bool	RequireSignInView                       bool	EnableNotifyMail                        bool	EnableBasicAuth                         bool	EnableReverseProxyAuth                  bool	EnableReverseProxyAuthAPI               bool	EnableReverseProxyAutoRegister          bool	EnableReverseProxyEmail                 bool	EnableReverseProxyFullName              bool	EnableCaptcha                           bool	RequireCaptchaForLogin                  bool	RequireExternalRegistrationCaptcha      bool	RequireExternalRegistrationPassword     bool	CaptchaType                             string	RecaptchaSecret                         string	RecaptchaSitekey                        string	RecaptchaURL                            string	CfTurnstileSecret                       string	CfTurnstileSitekey                      string	HcaptchaSecret                          string	HcaptchaSitekey                         string	McaptchaSecret                          string	McaptchaSitekey                         string	McaptchaURL                             string	DefaultKeepEmailPrivate                 bool	DefaultAllowCreateOrganization          bool	DefaultUserIsRestricted                 bool	AllowDotsInUsernames                    bool	EnableTimetracking                      bool	DefaultEnableTimetracking               bool	DefaultEnableDependencies               bool	AllowCrossRepositoryDependencies        bool	DefaultAllowOnlyContributorsToTrackTime bool	NoReplyAddress                          string	UserLocationMapURL                      string	EnableUserHeatmap                       bool	AutoWatchNewRepos                       bool	AutoWatchOnChanges                      bool	DefaultOrgMemberVisible                 bool	UserDeleteWithCommentsMaxTime           time.Duration	ValidSiteURLSchemes                     []string	UsernameCooldownPeriod                  int64	MaxUserRedirects                        int64 	// OpenID settings	EnableOpenIDSignIn bool	EnableOpenIDSignUp bool	OpenIDWhitelist    []*regexp.Regexp	OpenIDBlacklist    []*regexp.Regexp 	// Explore page settings	Explore struct {		RequireSigninView        bool `ini:"REQUIRE_SIGNIN_VIEW"`		DisableUsersPage         bool `ini:"DISABLE_USERS_PAGE"`		DisableOrganizationsPage bool `ini:"DISABLE_ORGANIZATIONS_PAGE"`		DisableCodePage          bool `ini:"DISABLE_CODE_PAGE"`	} `ini:"service.explore"`}{	AllowedUserVisibilityModesSlice: []bool{true, true, true},} // AllowedVisibility store in a 3 item bool array what is allowedtype AllowedVisibility []bool // IsAllowedVisibility check if a AllowedVisibility allow a specific VisibleTypefunc (a AllowedVisibility) IsAllowedVisibility(t structs.VisibleType) bool {	if int(t) >= len(a) {		return false	}	return a[t]} // ToVisibleTypeSlice convert a AllowedVisibility into a VisibleType slicefunc (a AllowedVisibility) ToVisibleTypeSlice() (result []structs.VisibleType) {	for i, v := range a {		if v {			result = append(result, structs.VisibleType(i))		}	}	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} // LoadServiceSetting loads the service settingsfunc 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)	Service.ResetPwdCodeLives = sec.Key("RESET_PASSWD_CODE_LIVE_MINUTES").MustInt(180)	Service.DisableRegistration = sec.Key("DISABLE_REGISTRATION").MustBool()	Service.AllowOnlyInternalRegistration = sec.Key("ALLOW_ONLY_INTERNAL_REGISTRATION").MustBool()	Service.AllowOnlyExternalRegistration = sec.Key("ALLOW_ONLY_EXTERNAL_REGISTRATION").MustBool()	if Service.AllowOnlyExternalRegistration && Service.AllowOnlyInternalRegistration {		log.Warn("ALLOW_ONLY_INTERNAL_REGISTRATION and ALLOW_ONLY_EXTERNAL_REGISTRATION are true - disabling registration")		Service.DisableRegistration = true	}	if !sec.Key("REGISTER_EMAIL_CONFIRM").MustBool() {		Service.RegisterManualConfirm = sec.Key("REGISTER_MANUAL_CONFIRM").MustBool(false)	} else {		Service.RegisterManualConfirm = false	}	if sec.HasKey("EMAIL_DOMAIN_WHITELIST") {		deprecatedSetting(rootCfg, "service", "EMAIL_DOMAIN_WHITELIST", "service", "EMAIL_DOMAIN_ALLOWLIST", "1.21")	}	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 {		toAdd := make([]glob.Glob, 0, len(DisposableEmailDomains()))		for _, domain := range DisposableEmailDomains() {			domain = strings.ToLower(domain)			// Only add domains that aren't blocked yet.			if !slices.ContainsFunc(Service.EmailDomainBlockList, func(g glob.Glob) bool { return g.Match(domain) }) {				if g, err := glob.Compile(domain); err != nil {					log.Error("Error in disposable domain %s: %v", domain, err)				} else {					toAdd = append(toAdd, g)				}			}		}		Service.EmailDomainBlockList = append(Service.EmailDomainBlockList, toAdd...)	}	Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!Service.DisableRegistration && !Service.AllowOnlyExternalRegistration)	Service.EnableInternalSignIn = sec.Key("ENABLE_INTERNAL_SIGNIN").MustBool(true)	Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true)	Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()	Service.EnableBasicAuth = sec.Key("ENABLE_BASIC_AUTHENTICATION").MustBool(true)	Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool()	Service.EnableReverseProxyAuthAPI = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION_API").MustBool()	Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool()	Service.EnableReverseProxyEmail = sec.Key("ENABLE_REVERSE_PROXY_EMAIL").MustBool()	Service.EnableReverseProxyFullName = sec.Key("ENABLE_REVERSE_PROXY_FULL_NAME").MustBool()	Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool(false)	Service.RequireCaptchaForLogin = sec.Key("REQUIRE_CAPTCHA_FOR_LOGIN").MustBool(false)	Service.RequireExternalRegistrationCaptcha = sec.Key("REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA").MustBool(Service.EnableCaptcha)	Service.RequireExternalRegistrationPassword = sec.Key("REQUIRE_EXTERNAL_REGISTRATION_PASSWORD").MustBool()	Service.CaptchaType = sec.Key("CAPTCHA_TYPE").MustString(ImageCaptcha)	Service.RecaptchaSecret = sec.Key("RECAPTCHA_SECRET").MustString("")	Service.RecaptchaSitekey = sec.Key("RECAPTCHA_SITEKEY").MustString("")	Service.RecaptchaURL = sec.Key("RECAPTCHA_URL").MustString("https://www.google.com/recaptcha/")	Service.CfTurnstileSecret = sec.Key("CF_TURNSTILE_SECRET").MustString("")	Service.CfTurnstileSitekey = sec.Key("CF_TURNSTILE_SITEKEY").MustString("")	Service.HcaptchaSecret = sec.Key("HCAPTCHA_SECRET").MustString("")	Service.HcaptchaSitekey = sec.Key("HCAPTCHA_SITEKEY").MustString("")	Service.McaptchaURL = sec.Key("MCAPTCHA_URL").MustString("https://demo.mcaptcha.org/")	Service.McaptchaSecret = sec.Key("MCAPTCHA_SECRET").MustString("")	Service.McaptchaSitekey = sec.Key("MCAPTCHA_SITEKEY").MustString("")	Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()	Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true)	Service.DefaultUserIsRestricted = sec.Key("DEFAULT_USER_IS_RESTRICTED").MustBool(false)	Service.AllowDotsInUsernames = sec.Key("ALLOW_DOTS_IN_USERNAMES").MustBool(true)	Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true)	if Service.EnableTimetracking {		Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)	}	Service.DefaultEnableDependencies = sec.Key("DEFAULT_ENABLE_DEPENDENCIES").MustBool(true)	Service.AllowCrossRepositoryDependencies = sec.Key("ALLOW_CROSS_REPOSITORY_DEPENDENCIES").MustBool(true)	Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true)	Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply." + Domain)	Service.UserLocationMapURL = sec.Key("USER_LOCATION_MAP_URL").MustString("https://www.openstreetmap.org/search?query=")	Service.EnableUserHeatmap = sec.Key("ENABLE_USER_HEATMAP").MustBool(true)	Service.AutoWatchNewRepos = sec.Key("AUTO_WATCH_NEW_REPOS").MustBool(true)	Service.AutoWatchOnChanges = sec.Key("AUTO_WATCH_ON_CHANGES").MustBool(false)	modes := sec.Key("ALLOWED_USER_VISIBILITY_MODES").Strings(",")	if len(modes) != 0 {		Service.AllowedUserVisibilityModes = []string{}		Service.AllowedUserVisibilityModesSlice = []bool{false, false, false}		for _, sMode := range modes {			if tp, ok := structs.VisibilityModes[sMode]; ok { // remove unsupported modes				Service.AllowedUserVisibilityModes = append(Service.AllowedUserVisibilityModes, sMode)				Service.AllowedUserVisibilityModesSlice[tp] = true			} else {				log.Warn("ALLOWED_USER_VISIBILITY_MODES %s is unsupported", sMode)			}		}	} 	if len(Service.AllowedUserVisibilityModes) == 0 {		Service.AllowedUserVisibilityModes = []string{"public", "limited", "private"}		Service.AllowedUserVisibilityModesSlice = []bool{true, true, true}	} 	Service.DefaultUserVisibility = sec.Key("DEFAULT_USER_VISIBILITY").String()	if Service.DefaultUserVisibility == "" {		Service.DefaultUserVisibility = Service.AllowedUserVisibilityModes[0]	} else if !Service.AllowedUserVisibilityModesSlice[structs.VisibilityModes[Service.DefaultUserVisibility]] {		log.Warn("DEFAULT_USER_VISIBILITY %s is wrong or not in ALLOWED_USER_VISIBILITY_MODES, using first allowed", Service.DefaultUserVisibility)		Service.DefaultUserVisibility = Service.AllowedUserVisibilityModes[0]	}	Service.DefaultUserVisibilityMode = structs.VisibilityModes[Service.DefaultUserVisibility]	Service.DefaultOrgVisibility = sec.Key("DEFAULT_ORG_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes))	Service.DefaultOrgVisibilityMode = structs.VisibilityModes[Service.DefaultOrgVisibility]	Service.DefaultOrgMemberVisible = sec.Key("DEFAULT_ORG_MEMBER_VISIBLE").MustBool()	Service.UserDeleteWithCommentsMaxTime = sec.Key("USER_DELETE_WITH_COMMENTS_MAX_TIME").MustDuration(0)	sec.Key("VALID_SITE_URL_SCHEMES").MustString("http,https")	Service.ValidSiteURLSchemes = sec.Key("VALID_SITE_URL_SCHEMES").Strings(",")	schemes := make([]string, 0, len(Service.ValidSiteURLSchemes))	for _, scheme := range Service.ValidSiteURLSchemes {		scheme = strings.ToLower(strings.TrimSpace(scheme))		if scheme != "" {			schemes = append(schemes, scheme)		}	}	Service.ValidSiteURLSchemes = schemes	Service.UsernameCooldownPeriod = sec.Key("USERNAME_COOLDOWN_PERIOD").MustInt64(0) 	// Only set a default if USERNAME_COOLDOWN_PERIOD's feature is active.	maxUserRedirectsDefault := int64(0)	if Service.UsernameCooldownPeriod > 0 {		maxUserRedirectsDefault = 5	}	Service.MaxUserRedirects = sec.Key("MAX_USER_REDIRECTS").MustInt64(maxUserRedirectsDefault) 	mustMapSetting(rootCfg, "service.explore", &Service.Explore) 	loadOpenIDSetting(rootCfg)} func loadOpenIDSetting(rootCfg ConfigProvider) {	sec := rootCfg.Section("openid")	Service.EnableOpenIDSignIn = sec.Key("ENABLE_OPENID_SIGNIN").MustBool(!InstallLock)	Service.EnableOpenIDSignUp = sec.Key("ENABLE_OPENID_SIGNUP").MustBool(!Service.DisableRegistration && Service.EnableOpenIDSignIn)	pats := sec.Key("WHITELISTED_URIS").Strings(" ")	if len(pats) != 0 {		Service.OpenIDWhitelist = make([]*regexp.Regexp, len(pats))		for i, p := range pats {			Service.OpenIDWhitelist[i] = regexp.MustCompilePOSIX(p)		}	}	pats = sec.Key("BLACKLISTED_URIS").Strings(" ")	if len(pats) != 0 {		Service.OpenIDBlacklist = make([]*regexp.Regexp, len(pats))		for i, p := range pats {			Service.OpenIDBlacklist[i] = regexp.MustCompilePOSIX(p)		}	}}