1
0
Fork 0

Adding upstream version 2.52.6.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-17 06:50:16 +02:00
parent a960158181
commit 6d002e9543
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
441 changed files with 95392 additions and 0 deletions

243
middleware/csrf/config.go Normal file
View file

@ -0,0 +1,243 @@
package csrf
import (
"net/textproto"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/gofiber/fiber/v2/middleware/session"
"github.com/gofiber/fiber/v2/utils"
)
// Config defines the config for middleware.
type Config struct {
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c *fiber.Ctx) bool
// KeyLookup is a string in the form of "<source>:<key>" that is used
// to create an Extractor that extracts the token from the request.
// Possible values:
// - "header:<name>"
// - "query:<name>"
// - "param:<name>"
// - "form:<name>"
// - "cookie:<name>"
//
// Ignored if an Extractor is explicitly set.
//
// Optional. Default: "header:X-Csrf-Token"
KeyLookup string
// Name of the session cookie. This cookie will store session key.
// Optional. Default value "csrf_".
// Overridden if KeyLookup == "cookie:<name>"
CookieName string
// Domain of the CSRF cookie.
// Optional. Default value "".
CookieDomain string
// Path of the CSRF cookie.
// Optional. Default value "".
CookiePath string
// Indicates if CSRF cookie is secure.
// Optional. Default value false.
CookieSecure bool
// Indicates if CSRF cookie is HTTP only.
// Optional. Default value false.
CookieHTTPOnly bool
// Value of SameSite cookie.
// Optional. Default value "Lax".
CookieSameSite string
// Decides whether cookie should last for only the browser sesison.
// Ignores Expiration if set to true
CookieSessionOnly bool
// Expiration is the duration before csrf token will expire
//
// Optional. Default: 1 * time.Hour
Expiration time.Duration
// SingleUseToken indicates if the CSRF token be destroyed
// and a new one generated on each use.
//
// Optional. Default: false
SingleUseToken bool
// Store is used to store the state of the middleware
//
// Optional. Default: memory.New()
// Ignored if Session is set.
Storage fiber.Storage
// Session is used to store the state of the middleware
//
// Optional. Default: nil
// If set, the middleware will use the session store instead of the storage
Session *session.Store
// SessionKey is the key used to store the token in the session
//
// Default: "fiber.csrf.token"
SessionKey string
// Context key to store generated CSRF token into context.
// If left empty, token will not be stored in context.
//
// Optional. Default: ""
ContextKey interface{}
// KeyGenerator creates a new CSRF token
//
// Optional. Default: utils.UUID
KeyGenerator func() string
// Deprecated: Please use Expiration
CookieExpires time.Duration
// Deprecated: Please use Cookie* related fields
Cookie *fiber.Cookie
// Deprecated: Please use KeyLookup
TokenLookup string
// ErrorHandler is executed when an error is returned from fiber.Handler.
//
// Optional. Default: DefaultErrorHandler
ErrorHandler fiber.ErrorHandler
// Extractor returns the csrf token
//
// If set this will be used in place of an Extractor based on KeyLookup.
//
// Optional. Default will create an Extractor based on KeyLookup.
Extractor func(c *fiber.Ctx) (string, error)
// HandlerContextKey is used to store the CSRF Handler into context
//
// Default: "fiber.csrf.handler"
HandlerContextKey interface{}
}
const HeaderName = "X-Csrf-Token"
// ConfigDefault is the default config
var ConfigDefault = Config{
KeyLookup: "header:" + HeaderName,
CookieName: "csrf_",
CookieSameSite: "Lax",
Expiration: 1 * time.Hour,
KeyGenerator: utils.UUIDv4,
ErrorHandler: defaultErrorHandler,
Extractor: CsrfFromHeader(HeaderName),
SessionKey: "fiber.csrf.token",
HandlerContextKey: "fiber.csrf.handler",
}
// default ErrorHandler that process return error from fiber.Handler
func defaultErrorHandler(_ *fiber.Ctx, _ error) error {
return fiber.ErrForbidden
}
// Helper function to set default values
func configDefault(config ...Config) Config {
// Return default config if nothing provided
if len(config) < 1 {
return ConfigDefault
}
// Override default config
cfg := config[0]
// Set default values
if cfg.TokenLookup != "" {
log.Warn("[CSRF] TokenLookup is deprecated, please use KeyLookup")
cfg.KeyLookup = cfg.TokenLookup
}
if int(cfg.CookieExpires.Seconds()) > 0 {
log.Warn("[CSRF] CookieExpires is deprecated, please use Expiration")
cfg.Expiration = cfg.CookieExpires
}
if cfg.Cookie != nil {
log.Warn("[CSRF] Cookie is deprecated, please use Cookie* related fields")
if cfg.Cookie.Name != "" {
cfg.CookieName = cfg.Cookie.Name
}
if cfg.Cookie.Domain != "" {
cfg.CookieDomain = cfg.Cookie.Domain
}
if cfg.Cookie.Path != "" {
cfg.CookiePath = cfg.Cookie.Path
}
cfg.CookieSecure = cfg.Cookie.Secure
cfg.CookieHTTPOnly = cfg.Cookie.HTTPOnly
if cfg.Cookie.SameSite != "" {
cfg.CookieSameSite = cfg.Cookie.SameSite
}
}
if cfg.KeyLookup == "" {
cfg.KeyLookup = ConfigDefault.KeyLookup
}
if int(cfg.Expiration.Seconds()) <= 0 {
cfg.Expiration = ConfigDefault.Expiration
}
if cfg.CookieName == "" {
cfg.CookieName = ConfigDefault.CookieName
}
if cfg.CookieSameSite == "" {
cfg.CookieSameSite = ConfigDefault.CookieSameSite
}
if cfg.KeyGenerator == nil {
cfg.KeyGenerator = ConfigDefault.KeyGenerator
}
if cfg.ErrorHandler == nil {
cfg.ErrorHandler = ConfigDefault.ErrorHandler
}
if cfg.SessionKey == "" {
cfg.SessionKey = ConfigDefault.SessionKey
}
if cfg.HandlerContextKey == nil {
cfg.HandlerContextKey = ConfigDefault.HandlerContextKey
}
// Generate the correct extractor to get the token from the correct location
selectors := strings.Split(cfg.KeyLookup, ":")
const numParts = 2
if len(selectors) != numParts {
panic("[CSRF] KeyLookup must in the form of <source>:<key>")
}
if cfg.Extractor == nil {
// By default we extract from a header
cfg.Extractor = CsrfFromHeader(textproto.CanonicalMIMEHeaderKey(selectors[1]))
switch selectors[0] {
case "form":
cfg.Extractor = CsrfFromForm(selectors[1])
case "query":
cfg.Extractor = CsrfFromQuery(selectors[1])
case "param":
cfg.Extractor = CsrfFromParam(selectors[1])
case "cookie":
if cfg.Session == nil {
log.Warn("[CSRF] Cookie extractor is not recommended without a session store")
}
if cfg.CookieSameSite == "None" || cfg.CookieSameSite != "Lax" && cfg.CookieSameSite != "Strict" {
log.Warn("[CSRF] Cookie extractor is only recommended for use with SameSite=Lax or SameSite=Strict")
}
cfg.Extractor = CsrfFromCookie(selectors[1])
cfg.CookieName = selectors[1] // Cookie name is the same as the key
}
}
return cfg
}

239
middleware/csrf/csrf.go Normal file
View file

@ -0,0 +1,239 @@
package csrf
import (
"errors"
"net/url"
"reflect"
"strings"
"time"
"github.com/gofiber/fiber/v2"
)
var (
ErrTokenNotFound = errors.New("csrf token not found")
ErrTokenInvalid = errors.New("csrf token invalid")
ErrNoReferer = errors.New("referer not supplied")
ErrBadReferer = errors.New("referer invalid")
dummyValue = []byte{'+'}
)
type CSRFHandler struct {
config *Config
sessionManager *sessionManager
storageManager *storageManager
}
// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Set default config
cfg := configDefault(config...)
// Create manager to simplify storage operations ( see *_manager.go )
var sessionManager *sessionManager
var storageManager *storageManager
if cfg.Session != nil {
// Register the Token struct in the session store
cfg.Session.RegisterType(Token{})
sessionManager = newSessionManager(cfg.Session, cfg.SessionKey)
} else {
storageManager = newStorageManager(cfg.Storage)
}
// Return new handler
return func(c *fiber.Ctx) error {
// Don't execute middleware if Next returns true
if cfg.Next != nil && cfg.Next(c) {
return c.Next()
}
// Store the CSRF handler in the context if a context key is specified
if cfg.HandlerContextKey != "" {
c.Locals(cfg.HandlerContextKey, &CSRFHandler{
config: &cfg,
sessionManager: sessionManager,
storageManager: storageManager,
})
}
var token string
// Action depends on the HTTP method
switch c.Method() {
case fiber.MethodGet, fiber.MethodHead, fiber.MethodOptions, fiber.MethodTrace:
cookieToken := c.Cookies(cfg.CookieName)
if cookieToken != "" {
raw := getRawFromStorage(c, cookieToken, cfg, sessionManager, storageManager)
if raw != nil {
token = cookieToken // Token is valid, safe to set it
}
}
default:
// Assume that anything not defined as 'safe' by RFC7231 needs protection
// Enforce an origin check for HTTPS connections.
if c.Protocol() == "https" {
if err := refererMatchesHost(c); err != nil {
return cfg.ErrorHandler(c, err)
}
}
// Extract token from client request i.e. header, query, param, form or cookie
extractedToken, err := cfg.Extractor(c)
if err != nil {
return cfg.ErrorHandler(c, err)
}
if extractedToken == "" {
return cfg.ErrorHandler(c, ErrTokenNotFound)
}
// If not using CsrfFromCookie extractor, check that the token matches the cookie
// This is to prevent CSRF attacks by using a Double Submit Cookie method
// Useful when we do not have access to the users Session
if !isCsrfFromCookie(cfg.Extractor) && !compareStrings(extractedToken, c.Cookies(cfg.CookieName)) {
return cfg.ErrorHandler(c, ErrTokenInvalid)
}
raw := getRawFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
if raw == nil {
// If token is not in storage, expire the cookie
expireCSRFCookie(c, cfg)
// and return an error
return cfg.ErrorHandler(c, ErrTokenNotFound)
}
if cfg.SingleUseToken {
// If token is single use, delete it from storage
deleteTokenFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
} else {
token = extractedToken // Token is valid, safe to set it
}
}
// Generate CSRF token if not exist
if token == "" {
// And generate a new token
token = cfg.KeyGenerator()
}
// Create or extend the token in the storage
createOrExtendTokenInStorage(c, token, cfg, sessionManager, storageManager)
// Update the CSRF cookie
updateCSRFCookie(c, cfg, token)
// Tell the browser that a new header value is generated
c.Vary(fiber.HeaderCookie)
// Store the token in the context if a context key is specified
if cfg.ContextKey != nil {
c.Locals(cfg.ContextKey, token)
}
// Continue stack
return c.Next()
}
}
// getRawFromStorage returns the raw value from the storage for the given token
// returns nil if the token does not exist, is expired or is invalid
func getRawFromStorage(c *fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) []byte {
if cfg.Session != nil {
return sessionManager.getRaw(c, token, dummyValue)
}
return storageManager.getRaw(token)
}
// createOrExtendTokenInStorage creates or extends the token in the storage
func createOrExtendTokenInStorage(c *fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) {
if cfg.Session != nil {
sessionManager.setRaw(c, token, dummyValue, cfg.Expiration)
} else {
storageManager.setRaw(token, dummyValue, cfg.Expiration)
}
}
func deleteTokenFromStorage(c *fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) {
if cfg.Session != nil {
sessionManager.delRaw(c)
} else {
storageManager.delRaw(token)
}
}
// Update CSRF cookie
// if expireCookie is true, the cookie will expire immediately
func updateCSRFCookie(c *fiber.Ctx, cfg Config, token string) {
setCSRFCookie(c, cfg, token, cfg.Expiration)
}
func expireCSRFCookie(c *fiber.Ctx, cfg Config) {
setCSRFCookie(c, cfg, "", -time.Hour)
}
func setCSRFCookie(c *fiber.Ctx, cfg Config, token string, expiry time.Duration) {
cookie := &fiber.Cookie{
Name: cfg.CookieName,
Value: token,
Domain: cfg.CookieDomain,
Path: cfg.CookiePath,
Secure: cfg.CookieSecure,
HTTPOnly: cfg.CookieHTTPOnly,
SameSite: cfg.CookieSameSite,
SessionOnly: cfg.CookieSessionOnly,
Expires: time.Now().Add(expiry),
}
// Set the CSRF cookie to the response
c.Cookie(cookie)
}
// DeleteToken removes the token found in the context from the storage
// and expires the CSRF cookie
func (handler *CSRFHandler) DeleteToken(c *fiber.Ctx) error {
// Get the config from the context
config := handler.config
if config == nil {
panic("CSRFHandler config not found in context")
}
// Extract token from the client request cookie
cookieToken := c.Cookies(config.CookieName)
if cookieToken == "" {
return config.ErrorHandler(c, ErrTokenNotFound)
}
// Remove the token from storage
deleteTokenFromStorage(c, cookieToken, *config, handler.sessionManager, handler.storageManager)
// Expire the cookie
expireCSRFCookie(c, *config)
return nil
}
// isCsrfFromCookie checks if the extractor is set to ExtractFromCookie
func isCsrfFromCookie(extractor interface{}) bool {
return reflect.ValueOf(extractor).Pointer() == reflect.ValueOf(CsrfFromCookie).Pointer()
}
// refererMatchesHost checks that the referer header matches the host header
// returns an error if the referer header is not present or is invalid
// returns nil if the referer header is valid
func refererMatchesHost(c *fiber.Ctx) error {
referer := strings.ToLower(c.Get(fiber.HeaderReferer))
if referer == "" {
return ErrNoReferer
}
refererURL, err := url.Parse(referer)
if err != nil {
return ErrBadReferer
}
if refererURL.Scheme == c.Protocol() && refererURL.Host == c.Hostname() {
return nil
}
return ErrBadReferer
}

1060
middleware/csrf/csrf_test.go Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,70 @@
package csrf
import (
"errors"
"github.com/gofiber/fiber/v2"
)
var (
ErrMissingHeader = errors.New("missing csrf token in header")
ErrMissingQuery = errors.New("missing csrf token in query")
ErrMissingParam = errors.New("missing csrf token in param")
ErrMissingForm = errors.New("missing csrf token in form")
ErrMissingCookie = errors.New("missing csrf token in cookie")
)
// csrfFromParam returns a function that extracts token from the url param string.
func CsrfFromParam(param string) func(c *fiber.Ctx) (string, error) {
return func(c *fiber.Ctx) (string, error) {
token := c.Params(param)
if token == "" {
return "", ErrMissingParam
}
return token, nil
}
}
// csrfFromForm returns a function that extracts a token from a multipart-form.
func CsrfFromForm(param string) func(c *fiber.Ctx) (string, error) {
return func(c *fiber.Ctx) (string, error) {
token := c.FormValue(param)
if token == "" {
return "", ErrMissingForm
}
return token, nil
}
}
// csrfFromCookie returns a function that extracts token from the cookie header.
func CsrfFromCookie(param string) func(c *fiber.Ctx) (string, error) {
return func(c *fiber.Ctx) (string, error) {
token := c.Cookies(param)
if token == "" {
return "", ErrMissingCookie
}
return token, nil
}
}
// csrfFromHeader returns a function that extracts token from the request header.
func CsrfFromHeader(param string) func(c *fiber.Ctx) (string, error) {
return func(c *fiber.Ctx) (string, error) {
token := c.Get(param)
if token == "" {
return "", ErrMissingHeader
}
return token, nil
}
}
// csrfFromQuery returns a function that extracts token from the query string.
func CsrfFromQuery(param string) func(c *fiber.Ctx) (string, error) {
return func(c *fiber.Ctx) (string, error) {
token := c.Query(param)
if token == "" {
return "", ErrMissingQuery
}
return token, nil
}
}

View file

@ -0,0 +1,13 @@
package csrf
import (
"crypto/subtle"
)
func compareTokens(a, b []byte) bool {
return subtle.ConstantTimeCompare(a, b) == 1
}
func compareStrings(a, b string) bool {
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}

View file

@ -0,0 +1,68 @@
package csrf
import (
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/gofiber/fiber/v2/middleware/session"
)
type sessionManager struct {
key string
session *session.Store
}
func newSessionManager(s *session.Store, k string) *sessionManager {
// Create new storage handler
sessionManager := &sessionManager{
key: k,
}
if s != nil {
// Use provided storage if provided
sessionManager.session = s
}
return sessionManager
}
// get token from session
func (m *sessionManager) getRaw(c *fiber.Ctx, key string, raw []byte) []byte {
sess, err := m.session.Get(c)
if err != nil {
return nil
}
token, ok := sess.Get(m.key).(Token)
if ok {
if token.Expiration.Before(time.Now()) || key != token.Key || !compareTokens(raw, token.Raw) {
return nil
}
return token.Raw
}
return nil
}
// set token in session
func (m *sessionManager) setRaw(c *fiber.Ctx, key string, raw []byte, exp time.Duration) {
sess, err := m.session.Get(c)
if err != nil {
return
}
// the key is crucial in crsf and sometimes a reference to another value which can be reused later(pool/unsafe values concept), so a copy is made here
sess.Set(m.key, &Token{key, raw, time.Now().Add(exp)})
if err := sess.Save(); err != nil {
log.Warn("csrf: failed to save session: ", err)
}
}
// delete token from session
func (m *sessionManager) delRaw(c *fiber.Ctx) {
sess, err := m.session.Get(c)
if err != nil {
return
}
sess.Delete(m.key)
if err := sess.Save(); err != nil {
log.Warn("csrf: failed to save session: ", err)
}
}

View file

@ -0,0 +1,70 @@
package csrf
import (
"sync"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/internal/memory"
"github.com/gofiber/fiber/v2/utils"
)
// go:generate msgp
// msgp -file="storage_manager.go" -o="storage_manager_msgp.go" -tests=false -unexported
type item struct{}
//msgp:ignore manager
type storageManager struct {
pool sync.Pool
memory *memory.Storage
storage fiber.Storage
}
func newStorageManager(storage fiber.Storage) *storageManager {
// Create new storage handler
storageManager := &storageManager{
pool: sync.Pool{
New: func() interface{} {
return new(item)
},
},
}
if storage != nil {
// Use provided storage if provided
storageManager.storage = storage
} else {
// Fallback too memory storage
storageManager.memory = memory.New()
}
return storageManager
}
// get raw data from storage or memory
func (m *storageManager) getRaw(key string) []byte {
var raw []byte
if m.storage != nil {
raw, _ = m.storage.Get(key) //nolint:errcheck // TODO: Do not ignore error
} else {
raw, _ = m.memory.Get(key).([]byte) //nolint:errcheck // TODO: Do not ignore error
}
return raw
}
// set data to storage or memory
func (m *storageManager) setRaw(key string, raw []byte, exp time.Duration) {
if m.storage != nil {
_ = m.storage.Set(key, raw, exp) //nolint:errcheck // TODO: Do not ignore error
} else {
// the key is crucial in crsf and sometimes a reference to another value which can be reused later(pool/unsafe values concept), so a copy is made here
m.memory.Set(utils.CopyString(key), raw, exp)
}
}
// delete data from storage or memory
func (m *storageManager) delRaw(key string) {
if m.storage != nil {
_ = m.storage.Delete(key) //nolint:errcheck // TODO: Do not ignore error
} else {
m.memory.Delete(key)
}
}

View file

@ -0,0 +1,90 @@
package csrf
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"github.com/tinylib/msgp/msgp"
)
// DecodeMsg implements msgp.Decodable
func (z *item) DecodeMsg(dc *msgp.Reader) (err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, err = dc.ReadMapKeyPtr()
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
return
}
// EncodeMsg implements msgp.Encodable
func (z item) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 0
err = en.Append(0x80)
if err != nil {
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z item) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 0
o = append(o, 0x80)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *item) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z item) Msgsize() (s int) {
s = 1
return
}

11
middleware/csrf/token.go Normal file
View file

@ -0,0 +1,11 @@
package csrf
import (
"time"
)
type Token struct {
Key string `json:"key"`
Raw []byte `json:"raw"`
Expiration time.Time `json:"expiration"`
}