Skip to content
Draft
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
3 changes: 0 additions & 3 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -503,9 +503,6 @@ INTERNAL_TOKEN =
;; Password Hash algorithm, either "argon2", "pbkdf2", "scrypt" or "bcrypt"
;PASSWORD_HASH_ALGO = pbkdf2
;;
;; Set false to allow JavaScript to read CSRF cookie
;CSRF_COOKIE_HTTP_ONLY = true
;;
;; Validate against https://haveibeenpwned.com/Passwords to see if a password has been exposed
;PASSWORD_CHECK_PWN = false
;;
Expand Down
2 changes: 1 addition & 1 deletion modules/setting/markup.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ func newMarkupRenderer(name string, sec ConfigSection) {
}

// ATTENTION! at the moment, only a safe set like "allow-scripts" are allowed for sandbox mode.
// "allow-same-origin" should never be used, it leads to XSS attack, and it makes the JS in iframe can access parent window's config and CSRF token
// "allow-same-origin" should NEVER be used, it leads to XSS attack: makes the JS in iframe can access parent window's config and send requests with user's credentials.
renderContentSandbox := sec.Key("RENDER_CONTENT_SANDBOX").MustString("allow-scripts allow-popups")
if renderContentSandbox == "disabled" {
renderContentSandbox = ""
Expand Down
2 changes: 1 addition & 1 deletion modules/setting/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func loadOAuth2From(rootCfg ConfigProvider) {

// FIXME: at the moment, no matter oauth2 is enabled or not, it must generate a "oauth2 JWT_SECRET"
// Because this secret is also used as GeneralTokenSigningSecret (as a quick not-that-breaking fix for some legacy problems).
// Including: CSRF token, account validation token, etc ...
// Including: account validation token, etc ...
// In main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...)
jwtSecretBase64 := loadSecret(sec, "JWT_SECRET_URI", "JWT_SECRET")
if InstallLock {
Expand Down
3 changes: 0 additions & 3 deletions modules/setting/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ var (
PasswordCheckPwn bool
SuccessfulTokensCacheSize int
DisableQueryAuthToken bool
CSRFCookieName = "_csrf"
CSRFCookieHTTPOnly = true
RecordUserSignupMetadata = false
TwoFactorAuthEnforced = false
)
Expand Down Expand Up @@ -139,7 +137,6 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
log.Fatal("The provided password hash algorithm was invalid: %s", sec.Key("PASSWORD_HASH_ALGO").MustString(""))
}

CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true)
PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false)
SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20)

Expand Down
1 change: 0 additions & 1 deletion routers/common/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,4 @@ type VerifyOptions struct {
SignInRequired bool
SignOutRequired bool
AdminRequired bool
DisableCSRF bool
}
7 changes: 0 additions & 7 deletions routers/web/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ func autoSignIn(ctx *context.Context) (bool, error) {
return false, err
}

ctx.Csrf.PrepareForSessionUser(ctx)
return true, nil
}

Expand Down Expand Up @@ -357,9 +356,6 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
}

// force to generate a new CSRF token
ctx.Csrf.PrepareForSessionUser(ctx)

// Register last login
if err := user_service.UpdateUser(ctx, u, &user_service.UpdateOptions{SetLastLogin: true}); err != nil {
ctx.ServerError("UpdateUser", err)
Expand Down Expand Up @@ -403,7 +399,6 @@ func HandleSignOut(ctx *context.Context) {
_ = ctx.Session.Flush()
_ = ctx.Session.Destroy(ctx.Resp, ctx.Req)
ctx.DeleteSiteCookie(setting.CookieRememberName)
ctx.Csrf.DeleteCookie(ctx)
middleware.DeleteRedirectToCookie(ctx.Resp)
}

Expand Down Expand Up @@ -811,8 +806,6 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
return
}

ctx.Csrf.PrepareForSessionUser(ctx)

if err := resetLocale(ctx, user); err != nil {
ctx.ServerError("resetLocale", err)
return
Expand Down
3 changes: 0 additions & 3 deletions routers/web/auth/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,9 +393,6 @@ func handleOAuth2SignIn(ctx *context.Context, authSource *auth.Source, u *user_m
return
}

// force to generate a new CSRF token
ctx.Csrf.PrepareForSessionUser(ctx)

if err := resetLocale(ctx, u); err != nil {
ctx.ServerError("resetLocale", err)
return
Expand Down
2 changes: 1 addition & 1 deletion routers/web/githttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ func addOwnerRepoGitHTTPRouters(m *web.Router) {
m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38,62}}", repo.GetLooseObject)
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.pack", repo.GetPackFile)
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.idx", repo.GetIdxFile)
}, optSignInIgnoreCsrf, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb())
}, optSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb())
}
2 changes: 1 addition & 1 deletion routers/web/user/setting/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ func loadKeysData(ctx *context.Context) {
ctx.Data["GPGKeys"] = gpgkeys
tokenToSign := asymkey_model.VerificationToken(ctx.Doer, 1)

// generate a new aes cipher using the csrfToken
// generate a new aes cipher using the token
ctx.Data["TokenToSign"] = tokenToSign

principals, err := db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
Expand Down
45 changes: 25 additions & 20 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,13 @@ func webAuth(authMethod auth_service.Method) func(*context.Context) {
// ensure the session uid is deleted
_ = ctx.Session.Delete("uid")
}

ctx.Csrf.PrepareForSessionUser(ctx)
}
}

// verifyAuthWithOptions checks authentication according to options
func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Context) {
crossOrginProtection := http.NewCrossOriginProtection()

return func(ctx *context.Context) {
// Check prohibit login users.
if ctx.IsSigned {
Expand Down Expand Up @@ -178,9 +178,9 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont
return
}

if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == http.MethodPost {
ctx.Csrf.Validate(ctx)
if ctx.Written() {
if !options.SignOutRequired {
if err := crossOrginProtection.Check(ctx.Req); err != nil {
http.Error(ctx.Resp, err.Error(), http.StatusForbidden)
return
}
}
Expand Down Expand Up @@ -292,17 +292,20 @@ func Routes() *web.Router {
return routes
}

var optSignInIgnoreCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true})
// required to be signed in or signed out
var (
reqSignIn = verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true})
reqSignOut = verifyAuthWithOptions(&common.VerifyOptions{SignOutRequired: true})
)

// optional sign in (if signed in, use the user as doer, if not, no doer)
var (
optSignIn = verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict})
optExploreSignIn = verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView})
)

// registerWebRoutes register routes
func registerWebRoutes(m *web.Router) {
// required to be signed in or signed out
reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true})
reqSignOut := verifyAuthWithOptions(&common.VerifyOptions{SignOutRequired: true})
// optional sign in (if signed in, use the user as doer, if not, no doer)
optSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict})
optExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView})

validation.AddBindingRules()

openIDSignInEnabled := func(ctx *context.Context) {
Expand Down Expand Up @@ -489,7 +492,7 @@ func registerWebRoutes(m *web.Router) {
m.Post("/-/markup", reqSignIn, web.Bind(structs.MarkupOption{}), misc.Markup)

m.Get("/-/web-theme/list", misc.WebThemeList)
m.Post("/-/web-theme/apply", optSignInIgnoreCsrf, misc.WebThemeApply)
m.Post("/-/web-theme/apply", misc.WebThemeApply)

m.Group("/explore", func() {
m.Get("", func(ctx *context.Context) {
Expand Down Expand Up @@ -565,12 +568,14 @@ func registerWebRoutes(m *web.Router) {
m.Post("/grant", web.Bind(forms.GrantApplicationForm{}), auth.GrantApplicationOAuth)
// TODO manage redirection
m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
}, optSignInIgnoreCsrf, reqSignIn)
}, reqSignIn)

m.Methods("GET, POST, OPTIONS", "/userinfo", optionsCorsHandler(), optSignInIgnoreCsrf, auth.InfoOAuth)
m.Methods("POST, OPTIONS", "/access_token", optionsCorsHandler(), web.Bind(forms.AccessTokenForm{}), optSignInIgnoreCsrf, auth.AccessTokenOAuth)
m.Methods("GET, OPTIONS", "/keys", optionsCorsHandler(), optSignInIgnoreCsrf, auth.OIDCKeys)
m.Methods("POST, OPTIONS", "/introspect", optionsCorsHandler(), web.Bind(forms.IntrospectTokenForm{}), optSignInIgnoreCsrf, auth.IntrospectOAuth)
m.Group("", func() {
m.Methods("GET, POST, OPTIONS", "/userinfo", auth.InfoOAuth)
m.Methods("POST, OPTIONS", "/access_token", web.Bind(forms.AccessTokenForm{}), auth.AccessTokenOAuth)
m.Methods("GET, OPTIONS", "/keys", auth.OIDCKeys)
m.Methods("POST, OPTIONS", "/introspect", web.Bind(forms.IntrospectTokenForm{}), auth.IntrospectOAuth)
}, optSignIn, optionsCorsHandler())
}, oauth2Enabled)

m.Group("/user/settings", func() {
Expand Down Expand Up @@ -1653,7 +1658,7 @@ func registerWebRoutes(m *web.Router) {
m.Post("/action/{action:accept_transfer|reject_transfer}", reqSignIn, repo.ActionTransfer)
}, optSignIn, context.RepoAssignment)

common.AddOwnerRepoGitLFSRoutes(m, optSignInIgnoreCsrf, lfsServerEnabled) // "/{username}/{reponame}/{lfs-paths}": git-lfs support
common.AddOwnerRepoGitLFSRoutes(m, optSignIn, lfsServerEnabled) // "/{username}/{reponame}/{lfs-paths}": git-lfs support

addOwnerRepoGitHTTPRouters(m) // "/{username}/{reponame}/{git-paths}": git http support

Expand Down
6 changes: 0 additions & 6 deletions services/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
gitea_context "code.gitea.io/gitea/services/context"
user_service "code.gitea.io/gitea/services/user"
)

Expand Down Expand Up @@ -162,9 +161,4 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore
}

middleware.SetLocaleCookie(resp, user.Language, 0)

// force to generate a new CSRF token
if ctx := gitea_context.GetWebContext(req.Context()); ctx != nil {
ctx.Csrf.PrepareForSessionUser(ctx)
}
}
2 changes: 1 addition & 1 deletion services/context/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func APIContexter() func(http.Handler) http.Handler {

ctx.SetContextValue(apiContextKey, ctx)

// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
// FIXME: GLOBAL-PARSE-FORM: see more details in another FIXME comment
if ctx.Req.Method == http.MethodPost && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
if !ctx.ParseMultipartForm() {
return
Expand Down
22 changes: 4 additions & 18 deletions services/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ package context

import (
"context"
"encoding/hex"
"fmt"
"html/template"
"io"
"net/http"
"net/url"
"strings"
"time"

"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
Expand Down Expand Up @@ -48,7 +46,6 @@ type Context struct {
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`

Cache cache.StringCache
Csrf CSRFProtector
Flash *middleware.Flash
Session session.Store

Expand Down Expand Up @@ -143,18 +140,6 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context {
// Contexter initializes a classic context for a request.
func Contexter() func(next http.Handler) http.Handler {
rnd := templates.HTMLRenderer()
csrfOpts := CsrfOptions{
Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()),
Cookie: setting.CSRFCookieName,
Secure: setting.SessionConfig.Secure,
CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
CookieDomain: setting.SessionConfig.Domain,
CookiePath: setting.SessionConfig.CookiePath,
SameSite: setting.SessionConfig.SameSite,
}
if !setting.IsProd {
CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
base := NewBaseContext(resp, req)
Expand All @@ -167,8 +152,6 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.PageData = map[string]any{}
ctx.Data["PageData"] = ctx.PageData

ctx.Csrf = NewCSRFProtector(csrfOpts)

// get the last flash message from cookie
lastFlashCookie, lastFlashMsg := middleware.GetSiteCookieFlashMessage(ctx, ctx.Req, CookieNameFlash)
if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 {
Expand All @@ -184,7 +167,10 @@ func Contexter() func(next http.Handler) http.Handler {
}
})

// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
// FIXME: GLOBAL-PARSE-FORM: this ParseMultipartForm was used for parsing the csrf token from multipart/form-data
// We have dropped the csrf token, so ideally this global ParseMultipartForm should be removed.
// When removing this, we need to avoid regressions in the handler functions because Golang's http framework is quite fragile
// and developers sometimes need to manually prase the form before accessing some values.
if ctx.Req.Method == http.MethodPost && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
if !ctx.ParseMultipartForm() {
return
Expand Down
2 changes: 0 additions & 2 deletions services/context/context_cookie.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,11 @@ func removeSessionCookieHeader(w http.ResponseWriter) {
}

// SetSiteCookie convenience function to set most cookies consistently
// CSRF and a few others are the exception here
func (ctx *Context) SetSiteCookie(name, value string, maxAge int) {
middleware.SetSiteCookie(ctx.Resp, name, value, maxAge)
}

// DeleteSiteCookie convenience function to delete most cookies consistently
// CSRF and a few others are the exception here
func (ctx *Context) DeleteSiteCookie(name string) {
middleware.SetSiteCookie(ctx.Resp, name, "", -1)
}
Expand Down
Loading
Loading