2016-11-04 01:16:01 +03:00
// Copyright 2013 Martini Authors
// Copyright 2014 The Macaron Authors
2021-01-26 18:36:53 +03:00
// Copyright 2021 The Gitea Authors
2016-11-04 01:16:01 +03:00
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
2022-12-02 17:14:57 +03:00
// SPDX-License-Identifier: Apache-2.0
2016-11-04 01:16:01 +03:00
2021-01-26 18:36:53 +03:00
// a middleware that generates and validates CSRF tokens.
package context
2016-11-04 01:16:01 +03:00
import (
2022-04-01 11:47:50 +03:00
"encoding/base32"
"fmt"
2016-11-04 01:16:01 +03:00
"net/http"
2022-04-01 19:34:57 +03:00
"strconv"
2016-11-04 01:16:01 +03:00
"time"
2022-04-01 19:34:57 +03:00
"code.gitea.io/gitea/modules/log"
2021-07-08 16:57:24 +03:00
"code.gitea.io/gitea/modules/setting"
2022-04-01 11:47:50 +03:00
"code.gitea.io/gitea/modules/util"
2021-03-07 11:12:43 +03:00
"code.gitea.io/gitea/modules/web/middleware"
2016-11-04 01:16:01 +03:00
)
2022-04-08 08:21:05 +03:00
// CSRFProtector represents a CSRF protector and is used to get the current token and validate the token.
type CSRFProtector interface {
// GetHeaderName returns HTTP header to search for token.
2016-11-04 01:16:01 +03:00
GetHeaderName ( ) string
2022-04-08 08:21:05 +03:00
// GetFormName returns form value to search for token.
2016-11-04 01:16:01 +03:00
GetFormName ( ) string
2022-04-08 08:21:05 +03:00
// GetToken returns the token.
2016-11-04 01:16:01 +03:00
GetToken ( ) string
2022-04-08 08:21:05 +03:00
// Validate validates the token in http context.
Validate ( ctx * Context )
2023-04-13 22:45:33 +03:00
// DeleteCookie deletes the cookie
DeleteCookie ( ctx * Context )
2016-11-04 01:16:01 +03:00
}
2022-04-08 08:21:05 +03:00
type csrfProtector struct {
2023-04-13 22:45:33 +03:00
opt CsrfOptions
2016-11-04 01:16:01 +03:00
// Token generated to pass via header, cookie, or hidden form value.
Token string
// This value must be unique per user.
ID string
}
// GetHeaderName returns the name of the HTTP header for csrf token.
2022-04-08 08:21:05 +03:00
func ( c * csrfProtector ) GetHeaderName ( ) string {
2023-04-13 22:45:33 +03:00
return c . opt . Header
2016-11-04 01:16:01 +03:00
}
// GetFormName returns the name of the form value for csrf token.
2022-04-08 08:21:05 +03:00
func ( c * csrfProtector ) GetFormName ( ) string {
2023-04-13 22:45:33 +03:00
return c . opt . Form
2016-11-04 01:16:01 +03:00
}
// GetToken returns the current token. This is typically used
// to populate a hidden form in an HTML template.
2022-04-08 08:21:05 +03:00
func ( c * csrfProtector ) GetToken ( ) string {
2016-11-04 01:16:01 +03:00
return c . Token
}
2021-01-26 18:36:53 +03:00
// CsrfOptions maintains options to manage behavior of Generate.
type CsrfOptions struct {
2016-11-04 01:16:01 +03:00
// The global secret value used to generate Tokens.
Secret string
// HTTP header used to set and get token.
Header string
// Form value used to set and get token.
Form string
// Cookie value used to set and get token.
Cookie string
2019-07-12 16:57:31 +03:00
// Cookie domain.
CookieDomain string
2016-11-04 01:16:01 +03:00
// Cookie path.
2019-07-12 16:57:31 +03:00
CookiePath string
2021-01-26 18:36:53 +03:00
CookieHTTPOnly bool
// SameSite set the cookie SameSite type
SameSite http . SameSite
2016-11-04 01:16:01 +03:00
// Key used for getting the unique ID per user.
SessionKey string
2021-01-26 18:36:53 +03:00
// oldSessionKey saves old value corresponding to SessionKey.
oldSessionKey string
2022-04-08 08:21:05 +03:00
// If true, send token via X-Csrf-Token header.
2016-11-04 01:16:01 +03:00
SetHeader bool
// If true, send token via _csrf cookie.
SetCookie bool
// Set the Secure flag to true on the cookie.
Secure bool
// Disallow Origin appear in request header.
Origin bool
2022-04-08 08:21:05 +03:00
// Cookie lifetime. Default is 0
2021-01-26 18:36:53 +03:00
CookieLifeTime int
2016-11-04 01:16:01 +03:00
}
2022-04-08 08:21:05 +03:00
func prepareDefaultCsrfOptions ( opt CsrfOptions ) CsrfOptions {
if opt . Secret == "" {
2022-04-01 11:47:50 +03:00
randBytes , err := util . CryptoRandomBytes ( 8 )
if err != nil {
// this panic can be handled by the recover() in http handlers
panic ( fmt . Errorf ( "failed to generate random bytes: %w" , err ) )
}
opt . Secret = base32 . StdEncoding . EncodeToString ( randBytes )
2016-11-04 01:16:01 +03:00
}
2022-04-08 08:21:05 +03:00
if opt . Header == "" {
opt . Header = "X-Csrf-Token"
2016-11-04 01:16:01 +03:00
}
2022-04-08 08:21:05 +03:00
if opt . Form == "" {
2016-11-04 01:16:01 +03:00
opt . Form = "_csrf"
}
2022-04-08 08:21:05 +03:00
if opt . Cookie == "" {
2016-11-04 01:16:01 +03:00
opt . Cookie = "_csrf"
}
2022-04-08 08:21:05 +03:00
if opt . CookiePath == "" {
2016-11-04 01:16:01 +03:00
opt . CookiePath = "/"
}
2022-04-08 08:21:05 +03:00
if opt . SessionKey == "" {
2016-11-04 01:16:01 +03:00
opt . SessionKey = "uid"
}
2023-04-13 22:45:33 +03:00
if opt . CookieLifeTime == 0 {
opt . CookieLifeTime = int ( CsrfTokenTimeout . Seconds ( ) )
}
2021-01-26 18:36:53 +03:00
opt . oldSessionKey = "_old_" + opt . SessionKey
2016-11-04 01:16:01 +03:00
return opt
}
2023-04-13 22:45:33 +03:00
func newCsrfCookie ( c * csrfProtector , value string ) * http . Cookie {
return & http . Cookie {
Name : c . opt . Cookie ,
Value : value ,
Path : c . opt . CookiePath ,
Domain : c . opt . CookieDomain ,
MaxAge : c . opt . CookieLifeTime ,
Secure : c . opt . Secure ,
HttpOnly : c . opt . CookieHTTPOnly ,
SameSite : c . opt . SameSite ,
}
}
2022-04-08 08:21:05 +03:00
// PrepareCSRFProtector returns a CSRFProtector to be used for every request.
2016-11-04 01:16:01 +03:00
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
2022-04-08 08:21:05 +03:00
func PrepareCSRFProtector ( opt CsrfOptions , ctx * Context ) CSRFProtector {
opt = prepareDefaultCsrfOptions ( opt )
2023-04-13 22:45:33 +03:00
x := & csrfProtector { opt : opt }
2016-11-04 01:16:01 +03:00
2021-01-26 18:36:53 +03:00
if opt . Origin && len ( ctx . Req . Header . Get ( "Origin" ) ) > 0 {
return x
}
2016-11-04 01:16:01 +03:00
2021-01-26 18:36:53 +03:00
x . ID = "0"
2022-04-01 19:34:57 +03:00
uidAny := ctx . Session . Get ( opt . SessionKey )
if uidAny != nil {
switch uidVal := uidAny . ( type ) {
case string :
x . ID = uidVal
case int64 :
x . ID = strconv . FormatInt ( uidVal , 10 )
default :
log . Error ( "invalid uid type in session: %T" , uidAny )
}
2021-01-26 18:36:53 +03:00
}
2016-11-04 01:16:01 +03:00
2021-01-26 18:36:53 +03:00
oldUID := ctx . Session . Get ( opt . oldSessionKey )
2022-04-08 08:21:05 +03:00
uidChanged := oldUID == nil || oldUID . ( string ) != x . ID
2023-04-13 22:45:33 +03:00
cookieToken := ctx . GetSiteCookie ( opt . Cookie )
2022-04-08 08:21:05 +03:00
needsNew := true
if uidChanged {
2021-01-26 18:36:53 +03:00
_ = ctx . Session . Set ( opt . oldSessionKey , x . ID )
2022-04-08 08:21:05 +03:00
} else if cookieToken != "" {
// If cookie token presents, re-use existing unexpired token, else generate a new one.
if issueTime , ok := ParseCsrfToken ( cookieToken ) ; ok {
dur := time . Since ( issueTime ) // issueTime is not a monotonic-clock, the server time may change a lot to an early time.
if dur >= - CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval {
x . Token = cookieToken
needsNew = false
}
2016-11-04 01:16:01 +03:00
}
2021-01-26 18:36:53 +03:00
}
2016-11-04 01:16:01 +03:00
2021-01-26 18:36:53 +03:00
if needsNew {
// FIXME: actionId.
2023-04-13 22:45:33 +03:00
x . Token = GenerateCsrfToken ( x . opt . Secret , x . ID , "POST" , time . Now ( ) )
2021-01-26 18:36:53 +03:00
if opt . SetCookie {
2023-04-13 22:45:33 +03:00
cookie := newCsrfCookie ( x , x . Token )
ctx . Resp . Header ( ) . Add ( "Set-Cookie" , cookie . String ( ) )
2016-11-04 01:16:01 +03:00
}
}
2021-01-26 18:36:53 +03:00
if opt . SetHeader {
ctx . Resp . Header ( ) . Add ( opt . Header , x . Token )
}
return x
2016-11-04 01:16:01 +03:00
}
2022-04-08 08:21:05 +03:00
func ( c * csrfProtector ) validateToken ( ctx * Context , token string ) {
2023-04-13 22:45:33 +03:00
if ! ValidCsrfToken ( token , c . opt . Secret , c . ID , "POST" , time . Now ( ) ) {
c . DeleteCookie ( ctx )
2022-04-08 08:21:05 +03:00
if middleware . IsAPIPath ( ctx . Req ) {
// currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
http . Error ( ctx . Resp , "Invalid CSRF token." , http . StatusBadRequest )
} else {
2021-07-08 16:57:24 +03:00
ctx . Flash . Error ( ctx . Tr ( "error.invalid_csrf" ) )
ctx . Redirect ( setting . AppSubURL + "/" )
2016-11-04 01:16:01 +03:00
}
}
2022-04-08 08:21:05 +03:00
}
// Validate should be used as a per route middleware. It attempts to get a token from an "X-Csrf-Token"
// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated.
// If this validation fails, custom Error is sent in the reply.
// If neither a header nor form value is found, http.StatusBadRequest is sent.
func ( c * csrfProtector ) Validate ( ctx * Context ) {
if token := ctx . Req . Header . Get ( c . GetHeaderName ( ) ) ; token != "" {
c . validateToken ( ctx , token )
2016-11-04 01:16:01 +03:00
return
}
2022-04-08 08:21:05 +03:00
if token := ctx . Req . FormValue ( c . GetFormName ( ) ) ; token != "" {
c . validateToken ( ctx , token )
2021-07-08 16:57:24 +03:00
return
}
2022-04-08 08:21:05 +03:00
c . validateToken ( ctx , "" ) // no csrf token, use an empty token to respond error
2016-11-04 01:16:01 +03:00
}
2023-04-13 22:45:33 +03:00
func ( c * csrfProtector ) DeleteCookie ( ctx * Context ) {
if c . opt . SetCookie {
cookie := newCsrfCookie ( c , "" )
cookie . MaxAge = - 1
ctx . Resp . Header ( ) . Add ( "Set-Cookie" , cookie . String ( ) )
}
}