loading up the forgejo repo on tangled to test page performance
1// Copyright 2019 The Gitea Authors. All rights reserved.
2// Copyright 2025 The Forgejo Authors. All rights reserved
3// SPDX-License-Identifier: MIT
4
5package setting
6
7import (
8 "net/url"
9 "regexp"
10 "slices"
11 "strings"
12 "time"
13
14 "forgejo.org/modules/log"
15 "forgejo.org/modules/structs"
16
17 "github.com/gobwas/glob"
18)
19
20// enumerates all the types of captchas
21const (
22 ImageCaptcha = "image"
23 ReCaptcha = "recaptcha"
24 HCaptcha = "hcaptcha"
25 MCaptcha = "mcaptcha"
26 CfTurnstile = "cfturnstile"
27)
28
29// Service settings
30var Service = struct {
31 DefaultUserVisibility string
32 DefaultUserVisibilityMode structs.VisibleType
33 AllowedUserVisibilityModes []string
34 AllowedUserVisibilityModesSlice AllowedVisibility `ini:"-"`
35 DefaultOrgVisibility string
36 DefaultOrgVisibilityMode structs.VisibleType
37 ActiveCodeLives int
38 ResetPwdCodeLives int
39 RegisterEmailConfirm bool
40 RegisterManualConfirm bool
41 EmailDomainAllowList []glob.Glob
42 EmailDomainBlockList []glob.Glob
43 EmailDomainBlockDisposable bool
44 DisableRegistration bool
45 AllowOnlyInternalRegistration bool
46 AllowOnlyExternalRegistration bool
47 ShowRegistrationButton bool
48 EnableInternalSignIn bool
49 ShowMilestonesDashboardPage bool
50 RequireSignInView bool
51 EnableNotifyMail bool
52 EnableBasicAuth bool
53 EnableReverseProxyAuth bool
54 EnableReverseProxyAuthAPI bool
55 EnableReverseProxyAutoRegister bool
56 EnableReverseProxyEmail bool
57 EnableReverseProxyFullName bool
58 EnableCaptcha bool
59 RequireCaptchaForLogin bool
60 RequireExternalRegistrationCaptcha bool
61 RequireExternalRegistrationPassword bool
62 CaptchaType string
63 RecaptchaSecret string
64 RecaptchaSitekey string
65 RecaptchaURL string
66 CfTurnstileSecret string
67 CfTurnstileSitekey string
68 HcaptchaSecret string
69 HcaptchaSitekey string
70 McaptchaSecret string
71 McaptchaSitekey string
72 McaptchaURL string
73 DefaultKeepEmailPrivate bool
74 DefaultAllowCreateOrganization bool
75 DefaultUserIsRestricted bool
76 AllowDotsInUsernames bool
77 EnableTimetracking bool
78 DefaultEnableTimetracking bool
79 DefaultEnableDependencies bool
80 AllowCrossRepositoryDependencies bool
81 DefaultAllowOnlyContributorsToTrackTime bool
82 NoReplyAddress string
83 UserLocationMapURL string
84 EnableUserHeatmap bool
85 AutoWatchNewRepos bool
86 AutoWatchOnChanges bool
87 DefaultOrgMemberVisible bool
88 UserDeleteWithCommentsMaxTime time.Duration
89 ValidSiteURLSchemes []string
90 UsernameCooldownPeriod int64
91 MaxUserRedirects int64
92
93 // OpenID settings
94 EnableOpenIDSignIn bool
95 EnableOpenIDSignUp bool
96 OpenIDWhitelist []*regexp.Regexp
97 OpenIDBlacklist []*regexp.Regexp
98
99 // Explore page settings
100 Explore struct {
101 RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"`
102 DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"`
103 DisableOrganizationsPage bool `ini:"DISABLE_ORGANIZATIONS_PAGE"`
104 DisableCodePage bool `ini:"DISABLE_CODE_PAGE"`
105 } `ini:"service.explore"`
106}{
107 AllowedUserVisibilityModesSlice: []bool{true, true, true},
108}
109
110// AllowedVisibility store in a 3 item bool array what is allowed
111type AllowedVisibility []bool
112
113// IsAllowedVisibility check if a AllowedVisibility allow a specific VisibleType
114func (a AllowedVisibility) IsAllowedVisibility(t structs.VisibleType) bool {
115 if int(t) >= len(a) {
116 return false
117 }
118 return a[t]
119}
120
121// ToVisibleTypeSlice convert a AllowedVisibility into a VisibleType slice
122func (a AllowedVisibility) ToVisibleTypeSlice() (result []structs.VisibleType) {
123 for i, v := range a {
124 if v {
125 result = append(result, structs.VisibleType(i))
126 }
127 }
128 return result
129}
130
131func CompileEmailGlobList(sec ConfigSection, keys ...string) (globs []glob.Glob) {
132 for _, key := range keys {
133 list := sec.Key(key).Strings(",")
134 for _, s := range list {
135 if g, err := glob.Compile(s); err == nil {
136 globs = append(globs, g)
137 } else {
138 log.Error("Skip invalid email allow/block list expression %q: %v", s, err)
139 }
140 }
141 }
142 return globs
143}
144
145// LoadServiceSetting loads the service settings
146func LoadServiceSetting() {
147 loadServiceFrom(CfgProvider)
148}
149
150func appURLAsGlob(fqdn string) (glob.Glob, error) {
151 localFqdn, err := url.ParseRequestURI(fqdn)
152 if err != nil {
153 log.Error("Error in EmailDomainAllowList: %v", err)
154 return nil, err
155 }
156 appFqdn, err := glob.Compile(localFqdn.Hostname(), ',')
157 if err != nil {
158 log.Error("Error in EmailDomainAllowList: %v", err)
159 return nil, err
160 }
161 return appFqdn, nil
162}
163
164func loadServiceFrom(rootCfg ConfigProvider) {
165 sec := rootCfg.Section("service")
166 Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180)
167 Service.ResetPwdCodeLives = sec.Key("RESET_PASSWD_CODE_LIVE_MINUTES").MustInt(180)
168 Service.DisableRegistration = sec.Key("DISABLE_REGISTRATION").MustBool()
169 Service.AllowOnlyInternalRegistration = sec.Key("ALLOW_ONLY_INTERNAL_REGISTRATION").MustBool()
170 Service.AllowOnlyExternalRegistration = sec.Key("ALLOW_ONLY_EXTERNAL_REGISTRATION").MustBool()
171 if Service.AllowOnlyExternalRegistration && Service.AllowOnlyInternalRegistration {
172 log.Warn("ALLOW_ONLY_INTERNAL_REGISTRATION and ALLOW_ONLY_EXTERNAL_REGISTRATION are true - disabling registration")
173 Service.DisableRegistration = true
174 }
175 if !sec.Key("REGISTER_EMAIL_CONFIRM").MustBool() {
176 Service.RegisterManualConfirm = sec.Key("REGISTER_MANUAL_CONFIRM").MustBool(false)
177 } else {
178 Service.RegisterManualConfirm = false
179 }
180 if sec.HasKey("EMAIL_DOMAIN_WHITELIST") {
181 deprecatedSetting(rootCfg, "service", "EMAIL_DOMAIN_WHITELIST", "service", "EMAIL_DOMAIN_ALLOWLIST", "1.21")
182 }
183 emailDomainAllowList := CompileEmailGlobList(sec, "EMAIL_DOMAIN_WHITELIST", "EMAIL_DOMAIN_ALLOWLIST")
184
185 if len(emailDomainAllowList) > 0 && Federation.Enabled {
186 appURL, err := appURLAsGlob(AppURL)
187 if err == nil {
188 emailDomainAllowList = append(emailDomainAllowList, appURL)
189 }
190 }
191 Service.EmailDomainAllowList = emailDomainAllowList
192 Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST")
193 Service.EmailDomainBlockDisposable = sec.Key("EMAIL_DOMAIN_BLOCK_DISPOSABLE").MustBool(false)
194 if Service.EmailDomainBlockDisposable {
195 toAdd := make([]glob.Glob, 0, len(DisposableEmailDomains()))
196 for _, domain := range DisposableEmailDomains() {
197 domain = strings.ToLower(domain)
198 // Only add domains that aren't blocked yet.
199 if !slices.ContainsFunc(Service.EmailDomainBlockList, func(g glob.Glob) bool { return g.Match(domain) }) {
200 if g, err := glob.Compile(domain); err != nil {
201 log.Error("Error in disposable domain %s: %v", domain, err)
202 } else {
203 toAdd = append(toAdd, g)
204 }
205 }
206 }
207 Service.EmailDomainBlockList = append(Service.EmailDomainBlockList, toAdd...)
208 }
209 Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!Service.DisableRegistration && !Service.AllowOnlyExternalRegistration)
210 Service.EnableInternalSignIn = sec.Key("ENABLE_INTERNAL_SIGNIN").MustBool(true)
211 Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true)
212 Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
213 Service.EnableBasicAuth = sec.Key("ENABLE_BASIC_AUTHENTICATION").MustBool(true)
214 Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool()
215 Service.EnableReverseProxyAuthAPI = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION_API").MustBool()
216 Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool()
217 Service.EnableReverseProxyEmail = sec.Key("ENABLE_REVERSE_PROXY_EMAIL").MustBool()
218 Service.EnableReverseProxyFullName = sec.Key("ENABLE_REVERSE_PROXY_FULL_NAME").MustBool()
219 Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool(false)
220 Service.RequireCaptchaForLogin = sec.Key("REQUIRE_CAPTCHA_FOR_LOGIN").MustBool(false)
221 Service.RequireExternalRegistrationCaptcha = sec.Key("REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA").MustBool(Service.EnableCaptcha)
222 Service.RequireExternalRegistrationPassword = sec.Key("REQUIRE_EXTERNAL_REGISTRATION_PASSWORD").MustBool()
223 Service.CaptchaType = sec.Key("CAPTCHA_TYPE").MustString(ImageCaptcha)
224 Service.RecaptchaSecret = sec.Key("RECAPTCHA_SECRET").MustString("")
225 Service.RecaptchaSitekey = sec.Key("RECAPTCHA_SITEKEY").MustString("")
226 Service.RecaptchaURL = sec.Key("RECAPTCHA_URL").MustString("https://www.google.com/recaptcha/")
227 Service.CfTurnstileSecret = sec.Key("CF_TURNSTILE_SECRET").MustString("")
228 Service.CfTurnstileSitekey = sec.Key("CF_TURNSTILE_SITEKEY").MustString("")
229 Service.HcaptchaSecret = sec.Key("HCAPTCHA_SECRET").MustString("")
230 Service.HcaptchaSitekey = sec.Key("HCAPTCHA_SITEKEY").MustString("")
231 Service.McaptchaURL = sec.Key("MCAPTCHA_URL").MustString("https://demo.mcaptcha.org/")
232 Service.McaptchaSecret = sec.Key("MCAPTCHA_SECRET").MustString("")
233 Service.McaptchaSitekey = sec.Key("MCAPTCHA_SITEKEY").MustString("")
234 Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()
235 Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true)
236 Service.DefaultUserIsRestricted = sec.Key("DEFAULT_USER_IS_RESTRICTED").MustBool(false)
237 Service.AllowDotsInUsernames = sec.Key("ALLOW_DOTS_IN_USERNAMES").MustBool(true)
238 Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true)
239 if Service.EnableTimetracking {
240 Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)
241 }
242 Service.DefaultEnableDependencies = sec.Key("DEFAULT_ENABLE_DEPENDENCIES").MustBool(true)
243 Service.AllowCrossRepositoryDependencies = sec.Key("ALLOW_CROSS_REPOSITORY_DEPENDENCIES").MustBool(true)
244 Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true)
245 Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply." + Domain)
246 Service.UserLocationMapURL = sec.Key("USER_LOCATION_MAP_URL").MustString("https://www.openstreetmap.org/search?query=")
247 Service.EnableUserHeatmap = sec.Key("ENABLE_USER_HEATMAP").MustBool(true)
248 Service.AutoWatchNewRepos = sec.Key("AUTO_WATCH_NEW_REPOS").MustBool(true)
249 Service.AutoWatchOnChanges = sec.Key("AUTO_WATCH_ON_CHANGES").MustBool(false)
250 modes := sec.Key("ALLOWED_USER_VISIBILITY_MODES").Strings(",")
251 if len(modes) != 0 {
252 Service.AllowedUserVisibilityModes = []string{}
253 Service.AllowedUserVisibilityModesSlice = []bool{false, false, false}
254 for _, sMode := range modes {
255 if tp, ok := structs.VisibilityModes[sMode]; ok { // remove unsupported modes
256 Service.AllowedUserVisibilityModes = append(Service.AllowedUserVisibilityModes, sMode)
257 Service.AllowedUserVisibilityModesSlice[tp] = true
258 } else {
259 log.Warn("ALLOWED_USER_VISIBILITY_MODES %s is unsupported", sMode)
260 }
261 }
262 }
263
264 if len(Service.AllowedUserVisibilityModes) == 0 {
265 Service.AllowedUserVisibilityModes = []string{"public", "limited", "private"}
266 Service.AllowedUserVisibilityModesSlice = []bool{true, true, true}
267 }
268
269 Service.DefaultUserVisibility = sec.Key("DEFAULT_USER_VISIBILITY").String()
270 if Service.DefaultUserVisibility == "" {
271 Service.DefaultUserVisibility = Service.AllowedUserVisibilityModes[0]
272 } else if !Service.AllowedUserVisibilityModesSlice[structs.VisibilityModes[Service.DefaultUserVisibility]] {
273 log.Warn("DEFAULT_USER_VISIBILITY %s is wrong or not in ALLOWED_USER_VISIBILITY_MODES, using first allowed", Service.DefaultUserVisibility)
274 Service.DefaultUserVisibility = Service.AllowedUserVisibilityModes[0]
275 }
276 Service.DefaultUserVisibilityMode = structs.VisibilityModes[Service.DefaultUserVisibility]
277 Service.DefaultOrgVisibility = sec.Key("DEFAULT_ORG_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes))
278 Service.DefaultOrgVisibilityMode = structs.VisibilityModes[Service.DefaultOrgVisibility]
279 Service.DefaultOrgMemberVisible = sec.Key("DEFAULT_ORG_MEMBER_VISIBLE").MustBool()
280 Service.UserDeleteWithCommentsMaxTime = sec.Key("USER_DELETE_WITH_COMMENTS_MAX_TIME").MustDuration(0)
281 sec.Key("VALID_SITE_URL_SCHEMES").MustString("http,https")
282 Service.ValidSiteURLSchemes = sec.Key("VALID_SITE_URL_SCHEMES").Strings(",")
283 schemes := make([]string, 0, len(Service.ValidSiteURLSchemes))
284 for _, scheme := range Service.ValidSiteURLSchemes {
285 scheme = strings.ToLower(strings.TrimSpace(scheme))
286 if scheme != "" {
287 schemes = append(schemes, scheme)
288 }
289 }
290 Service.ValidSiteURLSchemes = schemes
291 Service.UsernameCooldownPeriod = sec.Key("USERNAME_COOLDOWN_PERIOD").MustInt64(0)
292
293 // Only set a default if USERNAME_COOLDOWN_PERIOD's feature is active.
294 maxUserRedirectsDefault := int64(0)
295 if Service.UsernameCooldownPeriod > 0 {
296 maxUserRedirectsDefault = 5
297 }
298 Service.MaxUserRedirects = sec.Key("MAX_USER_REDIRECTS").MustInt64(maxUserRedirectsDefault)
299
300 mustMapSetting(rootCfg, "service.explore", &Service.Explore)
301
302 loadOpenIDSetting(rootCfg)
303}
304
305func loadOpenIDSetting(rootCfg ConfigProvider) {
306 sec := rootCfg.Section("openid")
307 Service.EnableOpenIDSignIn = sec.Key("ENABLE_OPENID_SIGNIN").MustBool(!InstallLock)
308 Service.EnableOpenIDSignUp = sec.Key("ENABLE_OPENID_SIGNUP").MustBool(!Service.DisableRegistration && Service.EnableOpenIDSignIn)
309 pats := sec.Key("WHITELISTED_URIS").Strings(" ")
310 if len(pats) != 0 {
311 Service.OpenIDWhitelist = make([]*regexp.Regexp, len(pats))
312 for i, p := range pats {
313 Service.OpenIDWhitelist[i] = regexp.MustCompilePOSIX(p)
314 }
315 }
316 pats = sec.Key("BLACKLISTED_URIS").Strings(" ")
317 if len(pats) != 0 {
318 Service.OpenIDBlacklist = make([]*regexp.Regexp, len(pats))
319 for i, p := range pats {
320 Service.OpenIDBlacklist[i] = regexp.MustCompilePOSIX(p)
321 }
322 }
323}