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 (
2024-09-18 10:17:25 +03:00
"html/template"
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"
2022-04-01 11:47:50 +03:00
"code.gitea.io/gitea/modules/util"
2024-09-18 10:17:25 +03:00
)
const (
CsrfHeaderName = "X-Csrf-Token"
CsrfFormName = "_csrf"
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 {
2024-09-18 10:17:25 +03:00
// PrepareForSessionUser prepares the csrf protector for the current session user.
PrepareForSessionUser ( ctx * Context )
// Validate validates the csrf token in http context.
2022-04-08 08:21:05 +03:00
Validate ( ctx * Context )
2024-09-18 10:17:25 +03:00
// DeleteCookie deletes the csrf cookie
2023-04-13 22:45:33 +03:00
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
2024-09-18 10:17:25 +03:00
// id must be unique per user.
id string
// token is the valid one which wil be used by end user and passed via header, cookie, or hidden form value.
token string
2016-11-04 01:16:01 +03:00
}
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
// 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
// Set the Secure flag to true on the cookie.
Secure bool
2024-09-18 10:17:25 +03:00
// sessionKey is the key used for getting the unique ID per user.
sessionKey string
// oldSessionKey saves old value corresponding to sessionKey.
oldSessionKey string
2016-11-04 01:16:01 +03:00
}
2024-09-18 10:17:25 +03:00
func newCsrfCookie ( opt * CsrfOptions , value string ) * http . Cookie {
2023-04-13 22:45:33 +03:00
return & http . Cookie {
2024-09-18 10:17:25 +03:00
Name : opt . Cookie ,
2023-04-13 22:45:33 +03:00
Value : value ,
2024-09-18 10:17:25 +03:00
Path : opt . CookiePath ,
Domain : opt . CookieDomain ,
MaxAge : int ( CsrfTokenTimeout . Seconds ( ) ) ,
Secure : opt . Secure ,
HttpOnly : opt . CookieHTTPOnly ,
SameSite : opt . SameSite ,
2023-04-13 22:45:33 +03:00
}
}
2024-09-18 10:17:25 +03:00
func NewCSRFProtector ( opt CsrfOptions ) CSRFProtector {
if opt . Secret == "" {
panic ( "CSRF secret is empty but it must be set" ) // it shouldn't happen because it is always set in code
2021-01-26 18:36:53 +03:00
}
2024-09-18 10:17:25 +03:00
opt . Cookie = util . IfZero ( opt . Cookie , "_csrf" )
opt . CookiePath = util . IfZero ( opt . CookiePath , "/" )
opt . sessionKey = "uid"
opt . oldSessionKey = "_old_" + opt . sessionKey
return & csrfProtector { opt : opt }
}
2016-11-04 01:16:01 +03:00
2024-09-18 10:17:25 +03:00
func ( c * csrfProtector ) PrepareForSessionUser ( ctx * Context ) {
c . id = "0"
if uidAny := ctx . Session . Get ( c . opt . sessionKey ) ; uidAny != nil {
2022-04-01 19:34:57 +03:00
switch uidVal := uidAny . ( type ) {
case string :
2024-09-18 10:17:25 +03:00
c . id = uidVal
2022-04-01 19:34:57 +03:00
case int64 :
2024-09-18 10:17:25 +03:00
c . id = strconv . FormatInt ( uidVal , 10 )
2022-04-01 19:34:57 +03:00
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
2024-09-18 10:17:25 +03:00
oldUID := ctx . Session . Get ( c . opt . oldSessionKey )
uidChanged := oldUID == nil || oldUID . ( string ) != c . id
cookieToken := ctx . GetSiteCookie ( c . opt . Cookie )
2022-04-08 08:21:05 +03:00
needsNew := true
if uidChanged {
2024-09-18 10:17:25 +03:00
_ = ctx . Session . Set ( c . opt . oldSessionKey , c . 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 {
2024-09-18 10:17:25 +03:00
c . token = cookieToken
2022-04-08 08:21:05 +03:00
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 {
2024-09-18 10:17:25 +03:00
c . token = GenerateCsrfToken ( c . opt . Secret , c . id , "POST" , time . Now ( ) )
2024-10-10 06:48:21 +03:00
ctx . Resp . Header ( ) . Add ( "Set-Cookie" , newCsrfCookie ( & c . opt , c . token ) . String ( ) )
2016-11-04 01:16:01 +03:00
}
2024-09-18 10:17:25 +03:00
ctx . Data [ "CsrfToken" ] = c . token
ctx . Data [ "CsrfTokenHtml" ] = template . HTML ( ` <input type="hidden" name="_csrf" value=" ` + template . HTMLEscapeString ( c . token ) + ` "> ` )
2016-11-04 01:16:01 +03:00
}
2022-04-08 08:21:05 +03:00
func ( c * csrfProtector ) validateToken ( ctx * Context , token string ) {
2024-09-18 10:17:25 +03:00
if ! ValidCsrfToken ( token , c . opt . Secret , c . id , "POST" , time . Now ( ) ) {
2023-04-13 22:45:33 +03:00
c . DeleteCookie ( ctx )
2024-09-18 10:17:25 +03:00
// currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
// FIXME: distinguish what the response is for: HTML (web page) or JSON (fetch)
http . Error ( ctx . Resp , "Invalid CSRF token." , http . StatusBadRequest )
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.
2024-09-18 10:17:25 +03:00
// If this validation fails, http.StatusBadRequest is sent.
2022-04-08 08:21:05 +03:00
func ( c * csrfProtector ) Validate ( ctx * Context ) {
2024-09-18 10:17:25 +03:00
if token := ctx . Req . Header . Get ( CsrfHeaderName ) ; token != "" {
2022-04-08 08:21:05 +03:00
c . validateToken ( ctx , token )
2016-11-04 01:16:01 +03:00
return
}
2024-09-18 10:17:25 +03:00
if token := ctx . Req . FormValue ( CsrfFormName ) ; token != "" {
2022-04-08 08:21:05 +03:00
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 ) {
2024-09-18 10:17:25 +03:00
cookie := newCsrfCookie ( & c . opt , "" )
cookie . MaxAge = - 1
ctx . Resp . Header ( ) . Add ( "Set-Cookie" , cookie . String ( ) )
2023-04-13 22:45:33 +03:00
}