gitea

Зеркало из https://github.com/go-gitea/gitea
Форк
0
170 строк · 5.4 Кб
1
// Copyright 2013 Martini Authors
2
// Copyright 2014 The Macaron Authors
3
// Copyright 2021 The Gitea Authors
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License"): you may
6
// not use this file except in compliance with the License. You may obtain
7
// a copy of the License at
8
//
9
//     http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
// License for the specific language governing permissions and limitations
15
// under the License.
16
// SPDX-License-Identifier: Apache-2.0
17

18
// a middleware that generates and validates CSRF tokens.
19

20
package context
21

22
import (
23
	"html/template"
24
	"net/http"
25
	"strconv"
26
	"time"
27

28
	"code.gitea.io/gitea/modules/log"
29
	"code.gitea.io/gitea/modules/util"
30
)
31

32
const (
33
	CsrfHeaderName = "X-Csrf-Token"
34
	CsrfFormName   = "_csrf"
35
)
36

37
// CSRFProtector represents a CSRF protector and is used to get the current token and validate the token.
38
type CSRFProtector interface {
39
	// PrepareForSessionUser prepares the csrf protector for the current session user.
40
	PrepareForSessionUser(ctx *Context)
41
	// Validate validates the csrf token in http context.
42
	Validate(ctx *Context)
43
	// DeleteCookie deletes the csrf cookie
44
	DeleteCookie(ctx *Context)
45
}
46

47
type csrfProtector struct {
48
	opt CsrfOptions
49
	// id must be unique per user.
50
	id string
51
	// token is the valid one which wil be used by end user and passed via header, cookie, or hidden form value.
52
	token string
53
}
54

55
// CsrfOptions maintains options to manage behavior of Generate.
56
type CsrfOptions struct {
57
	// The global secret value used to generate Tokens.
58
	Secret string
59
	// Cookie value used to set and get token.
60
	Cookie string
61
	// Cookie domain.
62
	CookieDomain string
63
	// Cookie path.
64
	CookiePath     string
65
	CookieHTTPOnly bool
66
	// SameSite set the cookie SameSite type
67
	SameSite http.SameSite
68
	// Set the Secure flag to true on the cookie.
69
	Secure bool
70
	// sessionKey is the key used for getting the unique ID per user.
71
	sessionKey string
72
	// oldSessionKey saves old value corresponding to sessionKey.
73
	oldSessionKey string
74
}
75

76
func newCsrfCookie(opt *CsrfOptions, value string) *http.Cookie {
77
	return &http.Cookie{
78
		Name:     opt.Cookie,
79
		Value:    value,
80
		Path:     opt.CookiePath,
81
		Domain:   opt.CookieDomain,
82
		MaxAge:   int(CsrfTokenTimeout.Seconds()),
83
		Secure:   opt.Secure,
84
		HttpOnly: opt.CookieHTTPOnly,
85
		SameSite: opt.SameSite,
86
	}
87
}
88

89
func NewCSRFProtector(opt CsrfOptions) CSRFProtector {
90
	if opt.Secret == "" {
91
		panic("CSRF secret is empty but it must be set") // it shouldn't happen because it is always set in code
92
	}
93
	opt.Cookie = util.IfZero(opt.Cookie, "_csrf")
94
	opt.CookiePath = util.IfZero(opt.CookiePath, "/")
95
	opt.sessionKey = "uid"
96
	opt.oldSessionKey = "_old_" + opt.sessionKey
97
	return &csrfProtector{opt: opt}
98
}
99

100
func (c *csrfProtector) PrepareForSessionUser(ctx *Context) {
101
	c.id = "0"
102
	if uidAny := ctx.Session.Get(c.opt.sessionKey); uidAny != nil {
103
		switch uidVal := uidAny.(type) {
104
		case string:
105
			c.id = uidVal
106
		case int64:
107
			c.id = strconv.FormatInt(uidVal, 10)
108
		default:
109
			log.Error("invalid uid type in session: %T", uidAny)
110
		}
111
	}
112

113
	oldUID := ctx.Session.Get(c.opt.oldSessionKey)
114
	uidChanged := oldUID == nil || oldUID.(string) != c.id
115
	cookieToken := ctx.GetSiteCookie(c.opt.Cookie)
116

117
	needsNew := true
118
	if uidChanged {
119
		_ = ctx.Session.Set(c.opt.oldSessionKey, c.id)
120
	} else if cookieToken != "" {
121
		// If cookie token presents, re-use existing unexpired token, else generate a new one.
122
		if issueTime, ok := ParseCsrfToken(cookieToken); ok {
123
			dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time.
124
			if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval {
125
				c.token = cookieToken
126
				needsNew = false
127
			}
128
		}
129
	}
130

131
	if needsNew {
132
		// FIXME: actionId.
133
		c.token = GenerateCsrfToken(c.opt.Secret, c.id, "POST", time.Now())
134
		cookie := newCsrfCookie(&c.opt, c.token)
135
		ctx.Resp.Header().Add("Set-Cookie", cookie.String())
136
	}
137

138
	ctx.Data["CsrfToken"] = c.token
139
	ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + template.HTMLEscapeString(c.token) + `">`)
140
}
141

142
func (c *csrfProtector) validateToken(ctx *Context, token string) {
143
	if !ValidCsrfToken(token, c.opt.Secret, c.id, "POST", time.Now()) {
144
		c.DeleteCookie(ctx)
145
		// currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
146
		// FIXME: distinguish what the response is for: HTML (web page) or JSON (fetch)
147
		http.Error(ctx.Resp, "Invalid CSRF token.", http.StatusBadRequest)
148
	}
149
}
150

151
// Validate should be used as a per route middleware. It attempts to get a token from an "X-Csrf-Token"
152
// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated.
153
// If this validation fails, http.StatusBadRequest is sent.
154
func (c *csrfProtector) Validate(ctx *Context) {
155
	if token := ctx.Req.Header.Get(CsrfHeaderName); token != "" {
156
		c.validateToken(ctx, token)
157
		return
158
	}
159
	if token := ctx.Req.FormValue(CsrfFormName); token != "" {
160
		c.validateToken(ctx, token)
161
		return
162
	}
163
	c.validateToken(ctx, "") // no csrf token, use an empty token to respond error
164
}
165

166
func (c *csrfProtector) DeleteCookie(ctx *Context) {
167
	cookie := newCsrfCookie(&c.opt, "")
168
	cookie.MaxAge = -1
169
	ctx.Resp.Header().Add("Set-Cookie", cookie.String())
170
}
171

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.