Add samesite cookie options #16

Merged
techknowlogick merged 2 commits from :add-samesite into master 2020-11-12 04:27:16 +00:00
4 changed files with 148 additions and 6 deletions

2
go.mod
View File

@ -4,7 +4,7 @@ go 1.11
require (
gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727
gitea.com/macaron/macaron v1.5.0
gitea.com/macaron/macaron v1.5.1-0.20201027213641-0db5d4584804
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668
github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89
github.com/couchbase/gomemcached v0.1.0 // indirect

7
go.sum
View File

@ -2,8 +2,10 @@ gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e h1:r1en/D7xJmcY24VkHkjkcJ
gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e/go.mod h1:uJEsN4LQpeGYRCjuPXPZBClU7N5pWzGuyF4uqLpE/e0=
gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727 h1:ZF2Bd6rqVlwhIDhYiS0uGYcT+GaVNGjuKVJkTNqWMIs=
gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727/go.mod h1:h0OwsgcpJLSYtHcM5+Xciw9OEeuxi6ty4HDiO8C7aIY=
gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a h1:aOKEXkDTnh4euoH0so/THLXeHtQuqHmDPb1xEk6Ehok=
gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
gitea.com/macaron/macaron v1.5.0/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY=
gitea.com/macaron/macaron v1.5.1-0.20201027213641-0db5d4584804 h1:yUiJVZKzdXsBe2tumTAXHBZa1qPGoGXM3fBG4RJ5fQg=
gitea.com/macaron/macaron v1.5.1-0.20201027213641-0db5d4584804/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY=
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA=
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89 h1:uNLXQ6QO1TocD8BaN/KkRki0Xw0brCM1PKl/ZA5pgfs=
@ -67,6 +69,7 @@ github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
@ -76,6 +79,7 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -110,6 +114,7 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0=
gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=

100
secret.go Normal file
View File

@ -0,0 +1,100 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package session
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"io"
)
// NewSecret creates a new secret
func NewSecret() (string, error) {
return NewSecretWithLength(32)
}
// NewSecretWithLength creates a new secret for a given length
func NewSecretWithLength(length int64) (string, error) {
return randomString(length)
}
func randomBytes(len int64) ([]byte, error) {
b := make([]byte, len)
if _, err := rand.Read(b); err != nil {
return nil, err
}
return b, nil
}
func randomString(len int64) (string, error) {
b, err := randomBytes(len)
return base64.URLEncoding.EncodeToString(b), err
}
// AesEncrypt encrypts text and given key with AES.
func AesEncrypt(key, text []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
b := base64.StdEncoding.EncodeToString(text)
ciphertext := make([]byte, aes.BlockSize+len(b))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
return ciphertext, nil
}
// AesDecrypt decrypts text and given key with AES.
func AesDecrypt(key, text []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(text) < aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
iv := text[:aes.BlockSize]
text = text[aes.BlockSize:]
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(text, text)
data, err := base64.StdEncoding.DecodeString(string(text))
if err != nil {
return nil, err
}
return data, nil
}
// EncryptSecret encrypts a string with given key into a hex string
func EncryptSecret(key string, str string) (string, error) {
keyHash := sha256.Sum256([]byte(key))
plaintext := []byte(str)
ciphertext, err := AesEncrypt(keyHash[:], plaintext)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// DecryptSecret decrypts a previously encrypted hex string
func DecryptSecret(key string, cipherhex string) (string, error) {
keyHash := sha256.Sum256([]byte(key))
ciphertext, err := base64.StdEncoding.DecodeString(cipherhex)
if err != nil {
return "", err
}
plaintext, err := AesDecrypt(keyHash[:], ciphertext)
if err != nil {
return "", err
}
return string(plaintext), nil
}

View File

@ -22,9 +22,11 @@ import (
"fmt"
"net/http"
"net/url"
"strconv"
"time"
"gitea.com/macaron/macaron"
"gitea.com/macaron/macaron/cookie"
)
const _VERSION = "0.6.0"
@ -89,6 +91,8 @@ type Options struct {
Secure bool
// Cookie life time. Default is 0.
CookieLifeTime int
// SameSite set the cookie SameSite
SameSite http.SameSite
// Cookie domain name. Default is empty.
Domain string
// Session ID length. Default is 16.
@ -97,6 +101,8 @@ type Options struct {
Section string
// Ignore release for websocket. Default is false.
IgnoreReleaseForWebSocket bool
// FlashEncryptionKey sets the encryption key for flash messages
FlashEncryptionKey string
}
func prepareOptions(options []Options) Options {
@ -133,6 +139,9 @@ func prepareOptions(options []Options) Options {
if opt.CookieLifeTime == 0 {
opt.CookieLifeTime = sec.Key("COOKIE_LIFE_TIME").MustInt()
}
if opt.SameSite == 0 {
opt.SameSite = http.SameSite(sec.Key("SAME_SITE").MustInt())
}
if len(opt.Domain) == 0 {
opt.Domain = sec.Key("DOMAIN").String()
}
@ -142,6 +151,12 @@ func prepareOptions(options []Options) Options {
if !opt.IgnoreReleaseForWebSocket {
opt.IgnoreReleaseForWebSocket = sec.Key("IGNORE_RELEASE_FOR_WEBSOCKET").MustBool()
}
if len(opt.FlashEncryptionKey) == 0 {
opt.FlashEncryptionKey = sec.Key("FLASH_ENCRYPTION_KEY").MustString("")
}
if len(opt.FlashEncryptionKey) == 0 {
opt.FlashEncryptionKey, _ = NewSecret()
}
return opt
}
@ -163,21 +178,41 @@ func Sessioner(options ...Options) macaron.Handler {
}
// Get flash.
vals, _ := url.ParseQuery(ctx.GetCookie("macaron_flash"))
flashCookie := ctx.GetCookie("macaron_flash")
decrypted, _ := DecryptSecret(opt.FlashEncryptionKey, flashCookie)
vals, _ := url.ParseQuery(decrypted)
if len(vals) > 0 {
f := &Flash{Values: vals}
f.ErrorMsg = f.Get("error")
f.SuccessMsg = f.Get("success")
f.InfoMsg = f.Get("info")
f.WarningMsg = f.Get("warning")
ctx.Data["Flash"] = f
ctx.SetCookie("macaron_flash", "", -1, opt.CookiePath)
t, _ := strconv.ParseInt(f.Get("time"), 10, 64)
now := time.Now().Unix()
if now-t > 0 && now-t < 3600 {
ctx.Data["Flash"] = f
}
}
if len(flashCookie) > 0 {
ctx.SetCookie("macaron_flash", "", -1, opt.CookiePath,
cookie.Domain(opt.Domain),
cookie.HTTPOnly(true),
cookie.Secure(opt.Secure),
cookie.SameSite(opt.SameSite))
}
f := &Flash{ctx, url.Values{}, "", "", "", ""}
ctx.Resp.Before(func(macaron.ResponseWriter) {
f.Set("time", strconv.FormatInt(time.Now().Unix(), 10))
if flash := f.Encode(); len(flash) > 0 {
ctx.SetCookie("macaron_flash", flash, 0, opt.CookiePath)
encrypted, err := EncryptSecret(opt.FlashEncryptionKey, flash)
if err == nil {
ctx.SetCookie("macaron_flash", encrypted, 0, opt.CookiePath,
cookie.Domain(opt.Domain),
cookie.HTTPOnly(true),
cookie.Secure(opt.Secure),
cookie.SameSite(opt.SameSite))
}
}
})
@ -299,6 +334,7 @@ func (m *Manager) Start(ctx *macaron.Context) (RawStore, error) {
HttpOnly: true,
Secure: m.opt.Secure,
Domain: m.opt.Domain,
SameSite: m.opt.SameSite,
}
if m.opt.CookieLifeTime >= 0 {
cookie.MaxAge = m.opt.CookieLifeTime
@ -362,6 +398,7 @@ func (m *Manager) RegenerateId(ctx *macaron.Context) (sess RawStore, err error)
HttpOnly: true,
Secure: m.opt.Secure,
Domain: m.opt.Domain,
SameSite: m.opt.SameSite,
}
if m.opt.CookieLifeTime >= 0 {
cookie.MaxAge = m.opt.CookieLifeTime