Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 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, and it makes the JS in iframe can access parent window's config
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
8 changes: 4 additions & 4 deletions routers/common/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ func AuthShared(ctx *context.Base, sessionStore auth_service.SessionStore, authM

// VerifyOptions contains required or check options
type VerifyOptions struct {
SignInRequired bool
SignOutRequired bool
AdminRequired bool
DisableCSRF bool
SignInRequired bool
SignOutRequired bool
AdminRequired bool
DisableCrossOriginProtection 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())
}, optSignInNoCOP, 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
28 changes: 15 additions & 13 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 && !options.DisableCrossOriginProtection {
if err := crossOrginProtection.Check(ctx.Req); err != nil {
http.Error(ctx.Resp, err.Error(), http.StatusForbidden)
return
}
}
Expand Down Expand Up @@ -292,7 +292,7 @@ func Routes() *web.Router {
return routes
}

var optSignInIgnoreCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true})
var optSignInNoCOP = verifyAuthWithOptions(&common.VerifyOptions{DisableCrossOriginProtection: true})

// registerWebRoutes register routes
func registerWebRoutes(m *web.Router) {
Expand Down Expand Up @@ -489,7 +489,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", optSignInNoCOP, misc.WebThemeApply)

m.Group("/explore", func() {
m.Get("", func(ctx *context.Context) {
Expand Down Expand Up @@ -565,12 +565,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{}), optSignInNoCOP, auth.AccessTokenOAuth)
m.Methods("GET, OPTIONS", "/keys", auth.OIDCKeys)
m.Methods("POST, OPTIONS", "/introspect", web.Bind(forms.IntrospectTokenForm{}), auth.IntrospectOAuth)
}, optionsCorsHandler(), optSignInNoCOP)
}, oauth2Enabled)

m.Group("/user/settings", func() {
Expand Down Expand Up @@ -1653,7 +1655,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, optSignInNoCOP, 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.
// If request sends files, parse them here otherwise the Query() can't be parsed.
if ctx.Req.Method == http.MethodPost && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
if !ctx.ParseMultipartForm() {
return
Expand Down
19 changes: 1 addition & 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,7 @@ 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.
// If request sends files, parse them here otherwise the Query() can't be parsed
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