Added challenge middleware

This commit is contained in:
Samuel Hawksby-Robinson 2022-08-19 13:45:50 +01:00
parent 6b1a008719
commit 1e55797ec8
26 changed files with 2244 additions and 62 deletions

7
go.mod
View File

@ -73,11 +73,15 @@ require (
go.uber.org/zap v1.22.0
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
google.golang.org/protobuf v1.28.0
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/go-playground/validator.v9 v9.31.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3
)
require github.com/gorilla/sessions v1.2.1
require (
github.com/PuerkitoBio/goquery v1.6.1 // indirect
github.com/RoaringBitmap/roaring v0.9.4 // indirect
@ -135,6 +139,7 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
@ -254,8 +259,6 @@ require (
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
golang.org/x/tools v0.1.11 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/urfave/cli.v1 v1.20.0 // indirect

4
go.sum
View File

@ -1056,6 +1056,10 @@ github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=

View File

@ -9,18 +9,22 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/url"
"github.com/btcsuite/btcutil/base58"
)
type PairingClient struct {
*http.Client
PayloadManager
baseAddress *url.URL
certPEM []byte
serverPK *ecdsa.PublicKey
serverMode Mode
serverCert *x509.Certificate
baseAddress *url.URL
certPEM []byte
serverPK *ecdsa.PublicKey
serverMode Mode
serverCert *x509.Certificate
serverChallenge []byte
}
func NewPairingClient(c *ConnectionParams, config *PairingPayloadManagerConfig) (*PairingClient, error) {
@ -55,13 +59,18 @@ func NewPairingClient(c *ConnectionParams, config *PairingPayloadManagerConfig)
},
}
cj, err := cookiejar.New(nil)
if err != nil {
return nil, err
}
pm, err := NewPairingPayloadManager(c.aesKey, config)
if err != nil {
return nil, err
}
return &PairingClient{
Client: &http.Client{Transport: tr},
Client: &http.Client{Transport: tr, Jar: cj},
baseAddress: u,
certPEM: certPem,
serverCert: serverCert,
@ -76,6 +85,10 @@ func (c *PairingClient) PairAccount() error {
case Receiving:
return c.sendAccountData()
case Sending:
err := c.getChallenge()
if err != nil {
return err
}
return c.receiveAccountData()
default:
return fmt.Errorf("unrecognised server mode '%d'", c.serverMode)
@ -84,21 +97,43 @@ func (c *PairingClient) PairAccount() error {
func (c *PairingClient) sendAccountData() error {
c.baseAddress.Path = pairingReceive
_, err := c.Post(c.baseAddress.String(), "application/octet-stream", bytes.NewBuffer(c.PayloadManager.ToSend()))
resp, err := c.Post(c.baseAddress.String(), "application/octet-stream", bytes.NewBuffer(c.PayloadManager.ToSend()))
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status not ok, received '%s'", resp.Status)
}
return nil
}
func (c *PairingClient) receiveAccountData() error {
c.baseAddress.Path = pairingSend
resp, err := c.Get(c.baseAddress.String())
req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil)
if err != nil {
return err
}
if c.serverChallenge != nil {
ec, err := c.PayloadManager.EncryptPlain(c.serverChallenge)
if err != nil {
return err
}
req.Header.Set(sessionChallenge, base58.Encode(ec))
}
resp, err := c.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status not ok, received '%s'", resp.Status)
}
payload, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
@ -106,3 +141,18 @@ func (c *PairingClient) receiveAccountData() error {
return c.PayloadManager.Receive(payload)
}
func (c *PairingClient) getChallenge() error {
c.baseAddress.Path = pairingChallenge
resp, err := c.Get(c.baseAddress.String())
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status not ok, received '%s'", resp.Status)
}
c.serverChallenge, err = ioutil.ReadAll(resp.Body)
return err
}

View File

@ -116,7 +116,7 @@ func (tpsc *TestPairingServerComponents) SetupPairingServerComponents(t *testing
}
type MockEncryptOnlyPayloadManager struct {
pem *PayloadEncryptionManager
*PayloadEncryptionManager
}
func NewMockEncryptOnlyPayloadManager(aesKey []byte) (*MockEncryptOnlyPayloadManager, error) {
@ -126,7 +126,7 @@ func NewMockEncryptOnlyPayloadManager(aesKey []byte) (*MockEncryptOnlyPayloadMan
}
return &MockEncryptOnlyPayloadManager{
pem: pem,
pem,
}, nil
}
@ -138,21 +138,9 @@ func (m *MockEncryptOnlyPayloadManager) Mount() error {
return err
}
return m.pem.Encrypt(data)
return m.Encrypt(data)
}
func (m *MockEncryptOnlyPayloadManager) Receive(data []byte) error {
return m.pem.Decrypt(data)
}
func (m *MockEncryptOnlyPayloadManager) ToSend() []byte {
return m.pem.ToSend()
}
func (m *MockEncryptOnlyPayloadManager) Received() []byte {
return m.pem.Received()
}
func (m *MockEncryptOnlyPayloadManager) ResetPayload() {
m.pem.ResetPayload()
return m.Decrypt(data)
}

View File

@ -1,14 +1,18 @@
package server
import (
"bytes"
"crypto/rand"
"database/sql"
"io/ioutil"
"net/http"
"time"
"github.com/btcsuite/btcutil/base58"
"go.uber.org/zap"
"github.com/status-im/status-go/ipfs"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/identity/identicon"
"github.com/status-im/status-go/protocol/images"
)
@ -21,14 +25,19 @@ const (
ipfsPath = "/ipfs"
// Handler routes for pairing
pairingBase = "/pairing"
pairingSend = pairingBase + "/send"
pairingReceive = pairingBase + "/receive"
pairingBase = "/pairing"
pairingSend = pairingBase + "/send"
pairingReceive = pairingBase + "/receive"
pairingChallenge = pairingBase + "/challenge"
// Session names
sessionChallenge = "challenge"
sessionBlocked = "blocked"
)
type HandlerPatternMap map[string]http.HandlerFunc
func handleIdenticon(logger *zap.Logger) func(w http.ResponseWriter, r *http.Request) {
func handleIdenticon(logger *zap.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
pks, ok := r.URL.Query()["publicKey"]
if !ok || len(pks) == 0 {
@ -52,7 +61,7 @@ func handleIdenticon(logger *zap.Logger) func(w http.ResponseWriter, r *http.Req
}
}
func handleImage(db *sql.DB, logger *zap.Logger) func(w http.ResponseWriter, r *http.Request) {
func handleImage(db *sql.DB, logger *zap.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
messageIDs, ok := r.URL.Query()["messageId"]
if !ok || len(messageIDs) == 0 {
@ -85,7 +94,7 @@ func handleImage(db *sql.DB, logger *zap.Logger) func(w http.ResponseWriter, r *
}
}
func handleAudio(db *sql.DB, logger *zap.Logger) func(w http.ResponseWriter, r *http.Request) {
func handleAudio(db *sql.DB, logger *zap.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
messageIDs, ok := r.URL.Query()["messageId"]
if !ok || len(messageIDs) == 0 {
@ -114,7 +123,7 @@ func handleAudio(db *sql.DB, logger *zap.Logger) func(w http.ResponseWriter, r *
}
}
func handleIPFS(downloader *ipfs.Downloader, logger *zap.Logger) func(w http.ResponseWriter, r *http.Request) {
func handleIPFS(downloader *ipfs.Downloader, logger *zap.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
hashes, ok := r.URL.Query()["hash"]
if !ok || len(hashes) == 0 {
@ -140,7 +149,7 @@ func handleIPFS(downloader *ipfs.Downloader, logger *zap.Logger) func(w http.Res
}
}
func handlePairingReceive(ps *PairingServer) func(w http.ResponseWriter, r *http.Request) {
func handlePairingReceive(ps *PairingServer) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
payload, err := ioutil.ReadAll(r.Body)
if err != nil {
@ -154,7 +163,8 @@ func handlePairingReceive(ps *PairingServer) func(w http.ResponseWriter, r *http
}
}
func handlePairingSend(ps *PairingServer) func(w http.ResponseWriter, r *http.Request) {
func handlePairingSend(ps *PairingServer) http.HandlerFunc {
// TODO lock sending after one successful transfer
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
_, err := w.Write(ps.PayloadManager.ToSend())
@ -163,3 +173,86 @@ func handlePairingSend(ps *PairingServer) func(w http.ResponseWriter, r *http.Re
}
}
}
func challengeMiddleware(ps *PairingServer, next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
s, err := ps.cookieStore.Get(r, sessionChallenge)
if err != nil {
ps.logger.Error("ps.cookieStore.Get(r, pairingStoreChallenge)", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
}
blocked, ok := s.Values[sessionBlocked].(bool)
if ok && blocked {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
// If the request header doesn't include a challenge don't punish the client, just throw a 403
pc := r.Header.Get(sessionChallenge)
if pc == "" {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
c, err := common.Decrypt(base58.Decode(pc), ps.ek)
if err != nil {
ps.logger.Error("c, err := common.Decrypt(rc, ps.ek)", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return
}
// If the challenge is not in the session store don't punish the client, just throw a 403
challenge, ok := s.Values[sessionChallenge].([]byte)
if !ok {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
// Only if we have both a challenge in the session store and in the request header
// do we entertain blocking the client. Because then we know someone is trying to be sneaky.
if bytes.Compare(c, challenge) != 0 {
s.Values[sessionBlocked] = true
err = s.Save(r, w)
if err != nil {
ps.logger.Error("err = s.Save(r, w)", zap.Error(err))
}
http.Error(w, "forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
}
}
func handlePairingChallenge(ps *PairingServer) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
s, err := ps.cookieStore.Get(r, sessionChallenge)
if err != nil {
ps.logger.Error("ps.cookieStore.Get(r, pairingStoreChallenge)", zap.Error(err))
}
var challenge []byte
challenge, ok := s.Values[sessionChallenge].([]byte)
if !ok {
challenge = make([]byte, 64)
_, err = rand.Read(challenge)
if err != nil {
ps.logger.Error("_, err = rand.Read(auth)", zap.Error(err))
}
s.Values[sessionChallenge] = challenge
err = s.Save(r, w)
if err != nil {
ps.logger.Error("err = s.Save(r, w)", zap.Error(err))
}
}
w.Header().Set("Content-Type", "application/octet-stream")
_, err = w.Write(challenge)
if err != nil {
ps.logger.Error("_, err = w.Write(challenge)", zap.Error(err))
}
}
}

View File

@ -24,6 +24,7 @@ type PayloadManager interface {
ToSend() []byte
Received() []byte
ResetPayload()
EncryptPlain(plaintext []byte) ([]byte, error)
}
// PairingPayloadManagerConfig represents the initialisation parameters required for a PairingPayloadManager
@ -36,8 +37,8 @@ type PairingPayloadManagerConfig struct {
// PairingPayloadManager is responsible for the whole lifecycle of a PairingPayload
type PairingPayloadManager struct {
pp *PairingPayload
pem *PayloadEncryptionManager
pp *PairingPayload
*PayloadEncryptionManager
ppm *PairingPayloadMarshaller
ppr PayloadRepository
}
@ -53,10 +54,10 @@ func NewPairingPayloadManager(aesKey []byte, config *PairingPayloadManagerConfig
p := new(PairingPayload)
return &PairingPayloadManager{
pp: p,
pem: pem,
ppm: NewPairingPayloadMarshaller(p),
ppr: NewPairingPayloadRepository(p, config),
pp: p,
PayloadEncryptionManager: pem,
ppm: NewPairingPayloadMarshaller(p),
ppr: NewPairingPayloadRepository(p, config),
}, nil
}
@ -72,17 +73,17 @@ func (ppm *PairingPayloadManager) Mount() error {
return err
}
return ppm.pem.Encrypt(pb)
return ppm.Encrypt(pb)
}
// Receive takes a []byte representing raw data, parses and stores the data
func (ppm *PairingPayloadManager) Receive(data []byte) error {
err := ppm.pem.Decrypt(data)
err := ppm.Decrypt(data)
if err != nil {
return err
}
err = ppm.ppm.UnmarshalProtobuf(ppm.pem.Received())
err = ppm.ppm.UnmarshalProtobuf(ppm.Received())
if err != nil {
return err
}
@ -90,20 +91,10 @@ func (ppm *PairingPayloadManager) Receive(data []byte) error {
return ppm.ppr.StoreToSource()
}
// ToSend returns the result of Mount
func (ppm *PairingPayloadManager) ToSend() []byte {
return ppm.pem.ToSend()
}
// Received returns the decrypted input of Receive
func (ppm *PairingPayloadManager) Received() []byte {
return ppm.pem.Received()
}
// ResetPayload resets all payload state managed by the PairingPayloadManager
func (ppm *PairingPayloadManager) ResetPayload() {
ppm.pp.ResetPayload()
ppm.pem.ResetPayload()
ppm.PayloadEncryptionManager.ResetPayload()
}
// EncryptionPayload represents the plain text and encrypted text of payload data
@ -123,6 +114,10 @@ func NewPayloadEncryptionManager(aesKey []byte) (*PayloadEncryptionManager, erro
return &PayloadEncryptionManager{aesKey, new(EncryptionPayload), new(EncryptionPayload)}, nil
}
func (pem *PayloadEncryptionManager) EncryptPlain(plaintext []byte) ([]byte, error) {
return common.Encrypt(plaintext, pem.aesKey, rand.Reader)
}
func (pem *PayloadEncryptionManager) Encrypt(data []byte) error {
ep, err := common.Encrypt(data, pem.aesKey, rand.Reader)
if err != nil {

View File

@ -2,9 +2,12 @@ package server
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/tls"
"fmt"
"net"
"github.com/gorilla/sessions"
)
type PairingServer struct {
@ -14,6 +17,8 @@ type PairingServer struct {
pk *ecdsa.PublicKey
ek []byte
mode Mode
cookieStore *sessions.CookieStore
}
type Config struct {
@ -28,6 +33,22 @@ type Config struct {
*PairingPayloadManagerConfig
}
func makeCookieStore() (*sessions.CookieStore, error) {
auth := make([]byte, 64)
_, err := rand.Read(auth)
if err != nil {
return nil, err
}
enc := make([]byte, 32)
_, err = rand.Read(enc)
if err != nil {
return nil, err
}
return sessions.NewCookieStore(auth, enc), nil
}
// NewPairingServer returns a *PairingServer init from the given *Config
func NewPairingServer(config *Config) (*PairingServer, error) {
pm, err := NewPairingPayloadManager(config.EK, config.PairingPayloadManagerConfig)
@ -35,6 +56,11 @@ func NewPairingServer(config *Config) (*PairingServer, error) {
return nil, err
}
cs, err := makeCookieStore()
if err != nil {
return nil, err
}
return &PairingServer{Server: NewServer(
config.Cert,
config.Hostname,
@ -42,7 +68,9 @@ func NewPairingServer(config *Config) (*PairingServer, error) {
pk: config.PK,
ek: config.EK,
mode: config.Mode,
PayloadManager: pm}, nil
PayloadManager: pm,
cookieStore: cs,
}, nil
}
// MakeConnectionParams generates a *ConnectionParams based on the Server's current state
@ -76,11 +104,17 @@ func (s *PairingServer) StartPairing() error {
}
func (s *PairingServer) startReceivingAccountData() error {
s.SetHandlers(HandlerPatternMap{pairingReceive: handlePairingReceive(s)})
s.SetHandlers(HandlerPatternMap{
pairingReceive: handlePairingReceive(s),
pairingChallenge: handlePairingChallenge(s),
})
return s.Start()
}
func (s *PairingServer) startSendingAccountData() error {
s.SetHandlers(HandlerPatternMap{pairingSend: handlePairingSend(s)})
s.SetHandlers(HandlerPatternMap{
pairingSend: challengeMiddleware(s, handlePairingSend(s)),
pairingChallenge: handlePairingChallenge(s),
})
return s.Start()
}

View File

@ -2,6 +2,7 @@ package server
import (
"crypto/ecdsa"
"crypto/rand"
"testing"
"time"
@ -85,13 +86,13 @@ func (s *PairingServerSuite) TestPairingServer_StartPairing() {
switch m {
case Receiving:
s.Require().Equal(c.PayloadManager.(*MockEncryptOnlyPayloadManager).pem.toSend.plain, s.PS.Received())
s.Require().Equal(s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).pem.received.encrypted, c.PayloadManager.(*MockEncryptOnlyPayloadManager).pem.toSend.encrypted)
s.Require().Equal(c.PayloadManager.(*MockEncryptOnlyPayloadManager).toSend.plain, s.PS.Received())
s.Require().Equal(s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).received.encrypted, c.PayloadManager.(*MockEncryptOnlyPayloadManager).toSend.encrypted)
s.Require().Nil(s.PS.ToSend())
s.Require().Nil(c.Received())
case Sending:
s.Require().Equal(c.Received(), s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).pem.toSend.plain)
s.Require().Equal(c.PayloadManager.(*MockEncryptOnlyPayloadManager).pem.received.encrypted, s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).pem.toSend.encrypted)
s.Require().Equal(c.Received(), s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).toSend.plain)
s.Require().Equal(c.PayloadManager.(*MockEncryptOnlyPayloadManager).received.encrypted, s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).toSend.encrypted)
s.Require().Nil(c.ToSend())
s.Require().Nil(s.PS.Received())
}
@ -100,3 +101,95 @@ func (s *PairingServerSuite) TestPairingServer_StartPairing() {
s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).ResetPayload()
}
}
func (s *PairingServerSuite) sendingSetup() *PairingClient {
// Replace PairingServer.PayloadManager with a MockEncryptOnlyPayloadManager
pm, err := NewMockEncryptOnlyPayloadManager(s.EphemeralAES)
s.Require().NoError(err)
s.PS.PayloadManager = pm
s.PS.mode = Sending
err = s.PS.Mount()
s.Require().NoError(err)
err = s.PS.StartPairing()
s.Require().NoError(err)
// Give time for the sever to be ready, hacky I know, I'll iron this out
time.Sleep(10 * time.Millisecond)
cp, err := s.PS.MakeConnectionParams()
s.Require().NoError(err)
qr := cp.ToString()
// Client reads QR code and parses the connection string
ccp := new(ConnectionParams)
err = ccp.FromString(qr)
s.Require().NoError(err)
c, err := NewPairingClient(ccp, nil)
s.Require().NoError(err)
// Replace PairingClient.PayloadManager with a MockEncryptOnlyPayloadManager
c.PayloadManager, err = NewMockEncryptOnlyPayloadManager(s.EphemeralAES)
s.Require().NoError(err)
return c
}
func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware() {
c := s.sendingSetup()
// Attempt to get the private key data, this should fail because there is no challenge
err := c.receiveAccountData()
s.Require().Error(err)
s.Require().Equal("status not ok, received '403 Forbidden'", err.Error())
err = c.getChallenge()
s.Require().NoError(err)
challenge := c.serverChallenge
// This is NOT a mistake! Call c.getChallenge() twice to check that the client gets the same challenge
// the server will only generate 1 challenge per session per connection
err = c.getChallenge()
s.Require().NoError(err)
s.Require().Equal(challenge, c.serverChallenge)
// receiving account data should now work.
err = c.receiveAccountData()
s.Require().NoError(err)
}
func (s *PairingServerSuite) TestPairingServer_handlePairingChallengeMiddleware_block() {
c := s.sendingSetup()
// Attempt to get the private key data, this should fail because there is no challenge
err := c.receiveAccountData()
s.Require().Error(err)
s.Require().Equal("status not ok, received '403 Forbidden'", err.Error())
// Get the challenge
err = c.getChallenge()
s.Require().NoError(err)
// Simulate encrypting with a dodgy key, write some nonsense to the challenge field
c.serverChallenge = make([]byte, 64)
_, err = rand.Read(c.serverChallenge)
s.Require().NoError(err)
// Attempt again to get the account data, should fail
// behind the scenes the server will block the session if the client fails the challenge. There is no forgiveness!
err = c.receiveAccountData()
s.Require().Error(err)
s.Require().Equal("status not ok, received '403 Forbidden'", err.Error())
// Get the real challenge
err = c.getChallenge()
s.Require().NoError(err)
// Attempt to get the account data, should fail because the client is now blocked.
err = c.receiveAccountData()
s.Require().Error(err)
s.Require().Equal("status not ok, received '403 Forbidden'", err.Error())
}

19
vendor/github.com/gorilla/securecookie/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,19 @@
language: go
sudo: false
matrix:
include:
- go: 1.3
- go: 1.4
- go: 1.5
- go: 1.6
- go: 1.7
- go: tip
allow_failures:
- go: tip
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- go vet $(go list ./... | grep -v /vendor/)
- go test -v -race ./...

27
vendor/github.com/gorilla/securecookie/LICENSE generated vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

80
vendor/github.com/gorilla/securecookie/README.md generated vendored Normal file
View File

@ -0,0 +1,80 @@
securecookie
============
[![GoDoc](https://godoc.org/github.com/gorilla/securecookie?status.svg)](https://godoc.org/github.com/gorilla/securecookie) [![Build Status](https://travis-ci.org/gorilla/securecookie.png?branch=master)](https://travis-ci.org/gorilla/securecookie)
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/securecookie/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/securecookie?badge)
securecookie encodes and decodes authenticated and optionally encrypted
cookie values.
Secure cookies can't be forged, because their values are validated using HMAC.
When encrypted, the content is also inaccessible to malicious eyes. It is still
recommended that sensitive data not be stored in cookies, and that HTTPS be used
to prevent cookie [replay attacks](https://en.wikipedia.org/wiki/Replay_attack).
## Examples
To use it, first create a new SecureCookie instance:
```go
// Hash keys should be at least 32 bytes long
var hashKey = []byte("very-secret")
// Block keys should be 16 bytes (AES-128) or 32 bytes (AES-256) long.
// Shorter keys may weaken the encryption used.
var blockKey = []byte("a-lot-secret")
var s = securecookie.New(hashKey, blockKey)
```
The hashKey is required, used to authenticate the cookie value using HMAC.
It is recommended to use a key with 32 or 64 bytes.
The blockKey is optional, used to encrypt the cookie value -- set it to nil
to not use encryption. If set, the length must correspond to the block size
of the encryption algorithm. For AES, used by default, valid lengths are
16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
Strong keys can be created using the convenience function GenerateRandomKey().
Once a SecureCookie instance is set, use it to encode a cookie value:
```go
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
value := map[string]string{
"foo": "bar",
}
if encoded, err := s.Encode("cookie-name", value); err == nil {
cookie := &http.Cookie{
Name: "cookie-name",
Value: encoded,
Path: "/",
Secure: true,
HttpOnly: true,
}
http.SetCookie(w, cookie)
}
}
```
Later, use the same SecureCookie instance to decode and validate a cookie
value:
```go
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
if cookie, err := r.Cookie("cookie-name"); err == nil {
value := make(map[string]string)
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
}
}
}
```
We stored a map[string]string, but secure cookies can hold any value that
can be encoded using `encoding/gob`. To store custom types, they must be
registered first using gob.Register(). For basic types this is not needed;
it works out of the box. An optional JSON encoder that uses `encoding/json` is
available for types compatible with JSON.
## License
BSD licensed. See the LICENSE file for details.

61
vendor/github.com/gorilla/securecookie/doc.go generated vendored Normal file
View File

@ -0,0 +1,61 @@
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package securecookie encodes and decodes authenticated and optionally
encrypted cookie values.
Secure cookies can't be forged, because their values are validated using HMAC.
When encrypted, the content is also inaccessible to malicious eyes.
To use it, first create a new SecureCookie instance:
var hashKey = []byte("very-secret")
var blockKey = []byte("a-lot-secret")
var s = securecookie.New(hashKey, blockKey)
The hashKey is required, used to authenticate the cookie value using HMAC.
It is recommended to use a key with 32 or 64 bytes.
The blockKey is optional, used to encrypt the cookie value -- set it to nil
to not use encryption. If set, the length must correspond to the block size
of the encryption algorithm. For AES, used by default, valid lengths are
16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
Strong keys can be created using the convenience function GenerateRandomKey().
Once a SecureCookie instance is set, use it to encode a cookie value:
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
value := map[string]string{
"foo": "bar",
}
if encoded, err := s.Encode("cookie-name", value); err == nil {
cookie := &http.Cookie{
Name: "cookie-name",
Value: encoded,
Path: "/",
}
http.SetCookie(w, cookie)
}
}
Later, use the same SecureCookie instance to decode and validate a cookie
value:
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
if cookie, err := r.Cookie("cookie-name"); err == nil {
value := make(map[string]string)
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
}
}
}
We stored a map[string]string, but secure cookies can hold any value that
can be encoded using encoding/gob. To store custom types, they must be
registered first using gob.Register(). For basic types this is not needed;
it works out of the box.
*/
package securecookie

25
vendor/github.com/gorilla/securecookie/fuzz.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
// +build gofuzz
package securecookie
var hashKey = []byte("very-secret12345")
var blockKey = []byte("a-lot-secret1234")
var s = New(hashKey, blockKey)
type Cookie struct {
B bool
I int
S string
}
func Fuzz(data []byte) int {
datas := string(data)
var c Cookie
if err := s.Decode("fuzz", datas, &c); err != nil {
return 0
}
if _, err := s.Encode("fuzz", c); err != nil {
panic(err)
}
return 1
}

646
vendor/github.com/gorilla/securecookie/securecookie.go generated vendored Normal file
View File

@ -0,0 +1,646 @@
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package securecookie
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"encoding/gob"
"encoding/json"
"fmt"
"hash"
"io"
"strconv"
"strings"
"time"
)
// Error is the interface of all errors returned by functions in this library.
type Error interface {
error
// IsUsage returns true for errors indicating the client code probably
// uses this library incorrectly. For example, the client may have
// failed to provide a valid hash key, or may have failed to configure
// the Serializer adequately for encoding value.
IsUsage() bool
// IsDecode returns true for errors indicating that a cookie could not
// be decoded and validated. Since cookies are usually untrusted
// user-provided input, errors of this type should be expected.
// Usually, the proper action is simply to reject the request.
IsDecode() bool
// IsInternal returns true for unexpected errors occurring in the
// securecookie implementation.
IsInternal() bool
// Cause, if it returns a non-nil value, indicates that this error was
// propagated from some underlying library. If this method returns nil,
// this error was raised directly by this library.
//
// Cause is provided principally for debugging/logging purposes; it is
// rare that application logic should perform meaningfully different
// logic based on Cause. See, for example, the caveats described on
// (MultiError).Cause().
Cause() error
}
// errorType is a bitmask giving the error type(s) of an cookieError value.
type errorType int
const (
usageError = errorType(1 << iota)
decodeError
internalError
)
type cookieError struct {
typ errorType
msg string
cause error
}
func (e cookieError) IsUsage() bool { return (e.typ & usageError) != 0 }
func (e cookieError) IsDecode() bool { return (e.typ & decodeError) != 0 }
func (e cookieError) IsInternal() bool { return (e.typ & internalError) != 0 }
func (e cookieError) Cause() error { return e.cause }
func (e cookieError) Error() string {
parts := []string{"securecookie: "}
if e.msg == "" {
parts = append(parts, "error")
} else {
parts = append(parts, e.msg)
}
if c := e.Cause(); c != nil {
parts = append(parts, " - caused by: ", c.Error())
}
return strings.Join(parts, "")
}
var (
errGeneratingIV = cookieError{typ: internalError, msg: "failed to generate random iv"}
errNoCodecs = cookieError{typ: usageError, msg: "no codecs provided"}
errHashKeyNotSet = cookieError{typ: usageError, msg: "hash key is not set"}
errBlockKeyNotSet = cookieError{typ: usageError, msg: "block key is not set"}
errEncodedValueTooLong = cookieError{typ: usageError, msg: "the value is too long"}
errValueToDecodeTooLong = cookieError{typ: decodeError, msg: "the value is too long"}
errTimestampInvalid = cookieError{typ: decodeError, msg: "invalid timestamp"}
errTimestampTooNew = cookieError{typ: decodeError, msg: "timestamp is too new"}
errTimestampExpired = cookieError{typ: decodeError, msg: "expired timestamp"}
errDecryptionFailed = cookieError{typ: decodeError, msg: "the value could not be decrypted"}
errValueNotByte = cookieError{typ: decodeError, msg: "value not a []byte."}
errValueNotBytePtr = cookieError{typ: decodeError, msg: "value not a pointer to []byte."}
// ErrMacInvalid indicates that cookie decoding failed because the HMAC
// could not be extracted and verified. Direct use of this error
// variable is deprecated; it is public only for legacy compatibility,
// and may be privatized in the future, as it is rarely useful to
// distinguish between this error and other Error implementations.
ErrMacInvalid = cookieError{typ: decodeError, msg: "the value is not valid"}
)
// Codec defines an interface to encode and decode cookie values.
type Codec interface {
Encode(name string, value interface{}) (string, error)
Decode(name, value string, dst interface{}) error
}
// New returns a new SecureCookie.
//
// hashKey is required, used to authenticate values using HMAC. Create it using
// GenerateRandomKey(). It is recommended to use a key with 32 or 64 bytes.
//
// blockKey is optional, used to encrypt values. Create it using
// GenerateRandomKey(). The key length must correspond to the block size
// of the encryption algorithm. For AES, used by default, valid lengths are
// 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
// The default encoder used for cookie serialization is encoding/gob.
//
// Note that keys created using GenerateRandomKey() are not automatically
// persisted. New keys will be created when the application is restarted, and
// previously issued cookies will not be able to be decoded.
func New(hashKey, blockKey []byte) *SecureCookie {
s := &SecureCookie{
hashKey: hashKey,
blockKey: blockKey,
hashFunc: sha256.New,
maxAge: 86400 * 30,
maxLength: 4096,
sz: GobEncoder{},
}
if hashKey == nil {
s.err = errHashKeyNotSet
}
if blockKey != nil {
s.BlockFunc(aes.NewCipher)
}
return s
}
// SecureCookie encodes and decodes authenticated and optionally encrypted
// cookie values.
type SecureCookie struct {
hashKey []byte
hashFunc func() hash.Hash
blockKey []byte
block cipher.Block
maxLength int
maxAge int64
minAge int64
err error
sz Serializer
// For testing purposes, the function that returns the current timestamp.
// If not set, it will use time.Now().UTC().Unix().
timeFunc func() int64
}
// Serializer provides an interface for providing custom serializers for cookie
// values.
type Serializer interface {
Serialize(src interface{}) ([]byte, error)
Deserialize(src []byte, dst interface{}) error
}
// GobEncoder encodes cookie values using encoding/gob. This is the simplest
// encoder and can handle complex types via gob.Register.
type GobEncoder struct{}
// JSONEncoder encodes cookie values using encoding/json. Users who wish to
// encode complex types need to satisfy the json.Marshaller and
// json.Unmarshaller interfaces.
type JSONEncoder struct{}
// NopEncoder does not encode cookie values, and instead simply accepts a []byte
// (as an interface{}) and returns a []byte. This is particularly useful when
// you encoding an object upstream and do not wish to re-encode it.
type NopEncoder struct{}
// MaxLength restricts the maximum length, in bytes, for the cookie value.
//
// Default is 4096, which is the maximum value accepted by Internet Explorer.
func (s *SecureCookie) MaxLength(value int) *SecureCookie {
s.maxLength = value
return s
}
// MaxAge restricts the maximum age, in seconds, for the cookie value.
//
// Default is 86400 * 30. Set it to 0 for no restriction.
func (s *SecureCookie) MaxAge(value int) *SecureCookie {
s.maxAge = int64(value)
return s
}
// MinAge restricts the minimum age, in seconds, for the cookie value.
//
// Default is 0 (no restriction).
func (s *SecureCookie) MinAge(value int) *SecureCookie {
s.minAge = int64(value)
return s
}
// HashFunc sets the hash function used to create HMAC.
//
// Default is crypto/sha256.New.
func (s *SecureCookie) HashFunc(f func() hash.Hash) *SecureCookie {
s.hashFunc = f
return s
}
// BlockFunc sets the encryption function used to create a cipher.Block.
//
// Default is crypto/aes.New.
func (s *SecureCookie) BlockFunc(f func([]byte) (cipher.Block, error)) *SecureCookie {
if s.blockKey == nil {
s.err = errBlockKeyNotSet
} else if block, err := f(s.blockKey); err == nil {
s.block = block
} else {
s.err = cookieError{cause: err, typ: usageError}
}
return s
}
// Encoding sets the encoding/serialization method for cookies.
//
// Default is encoding/gob. To encode special structures using encoding/gob,
// they must be registered first using gob.Register().
func (s *SecureCookie) SetSerializer(sz Serializer) *SecureCookie {
s.sz = sz
return s
}
// Encode encodes a cookie value.
//
// It serializes, optionally encrypts, signs with a message authentication code,
// and finally encodes the value.
//
// The name argument is the cookie name. It is stored with the encoded value.
// The value argument is the value to be encoded. It can be any value that can
// be encoded using the currently selected serializer; see SetSerializer().
//
// It is the client's responsibility to ensure that value, when encoded using
// the current serialization/encryption settings on s and then base64-encoded,
// is shorter than the maximum permissible length.
func (s *SecureCookie) Encode(name string, value interface{}) (string, error) {
if s.err != nil {
return "", s.err
}
if s.hashKey == nil {
s.err = errHashKeyNotSet
return "", s.err
}
var err error
var b []byte
// 1. Serialize.
if b, err = s.sz.Serialize(value); err != nil {
return "", cookieError{cause: err, typ: usageError}
}
// 2. Encrypt (optional).
if s.block != nil {
if b, err = encrypt(s.block, b); err != nil {
return "", cookieError{cause: err, typ: usageError}
}
}
b = encode(b)
// 3. Create MAC for "name|date|value". Extra pipe to be used later.
b = []byte(fmt.Sprintf("%s|%d|%s|", name, s.timestamp(), b))
mac := createMac(hmac.New(s.hashFunc, s.hashKey), b[:len(b)-1])
// Append mac, remove name.
b = append(b, mac...)[len(name)+1:]
// 4. Encode to base64.
b = encode(b)
// 5. Check length.
if s.maxLength != 0 && len(b) > s.maxLength {
return "", errEncodedValueTooLong
}
// Done.
return string(b), nil
}
// Decode decodes a cookie value.
//
// It decodes, verifies a message authentication code, optionally decrypts and
// finally deserializes the value.
//
// The name argument is the cookie name. It must be the same name used when
// it was stored. The value argument is the encoded cookie value. The dst
// argument is where the cookie will be decoded. It must be a pointer.
func (s *SecureCookie) Decode(name, value string, dst interface{}) error {
if s.err != nil {
return s.err
}
if s.hashKey == nil {
s.err = errHashKeyNotSet
return s.err
}
// 1. Check length.
if s.maxLength != 0 && len(value) > s.maxLength {
return errValueToDecodeTooLong
}
// 2. Decode from base64.
b, err := decode([]byte(value))
if err != nil {
return err
}
// 3. Verify MAC. Value is "date|value|mac".
parts := bytes.SplitN(b, []byte("|"), 3)
if len(parts) != 3 {
return ErrMacInvalid
}
h := hmac.New(s.hashFunc, s.hashKey)
b = append([]byte(name+"|"), b[:len(b)-len(parts[2])-1]...)
if err = verifyMac(h, b, parts[2]); err != nil {
return err
}
// 4. Verify date ranges.
var t1 int64
if t1, err = strconv.ParseInt(string(parts[0]), 10, 64); err != nil {
return errTimestampInvalid
}
t2 := s.timestamp()
if s.minAge != 0 && t1 > t2-s.minAge {
return errTimestampTooNew
}
if s.maxAge != 0 && t1 < t2-s.maxAge {
return errTimestampExpired
}
// 5. Decrypt (optional).
b, err = decode(parts[1])
if err != nil {
return err
}
if s.block != nil {
if b, err = decrypt(s.block, b); err != nil {
return err
}
}
// 6. Deserialize.
if err = s.sz.Deserialize(b, dst); err != nil {
return cookieError{cause: err, typ: decodeError}
}
// Done.
return nil
}
// timestamp returns the current timestamp, in seconds.
//
// For testing purposes, the function that generates the timestamp can be
// overridden. If not set, it will return time.Now().UTC().Unix().
func (s *SecureCookie) timestamp() int64 {
if s.timeFunc == nil {
return time.Now().UTC().Unix()
}
return s.timeFunc()
}
// Authentication -------------------------------------------------------------
// createMac creates a message authentication code (MAC).
func createMac(h hash.Hash, value []byte) []byte {
h.Write(value)
return h.Sum(nil)
}
// verifyMac verifies that a message authentication code (MAC) is valid.
func verifyMac(h hash.Hash, value []byte, mac []byte) error {
mac2 := createMac(h, value)
// Check that both MACs are of equal length, as subtle.ConstantTimeCompare
// does not do this prior to Go 1.4.
if len(mac) == len(mac2) && subtle.ConstantTimeCompare(mac, mac2) == 1 {
return nil
}
return ErrMacInvalid
}
// Encryption -----------------------------------------------------------------
// encrypt encrypts a value using the given block in counter mode.
//
// A random initialization vector (http://goo.gl/zF67k) with the length of the
// block size is prepended to the resulting ciphertext.
func encrypt(block cipher.Block, value []byte) ([]byte, error) {
iv := GenerateRandomKey(block.BlockSize())
if iv == nil {
return nil, errGeneratingIV
}
// Encrypt it.
stream := cipher.NewCTR(block, iv)
stream.XORKeyStream(value, value)
// Return iv + ciphertext.
return append(iv, value...), nil
}
// decrypt decrypts a value using the given block in counter mode.
//
// The value to be decrypted must be prepended by a initialization vector
// (http://goo.gl/zF67k) with the length of the block size.
func decrypt(block cipher.Block, value []byte) ([]byte, error) {
size := block.BlockSize()
if len(value) > size {
// Extract iv.
iv := value[:size]
// Extract ciphertext.
value = value[size:]
// Decrypt it.
stream := cipher.NewCTR(block, iv)
stream.XORKeyStream(value, value)
return value, nil
}
return nil, errDecryptionFailed
}
// Serialization --------------------------------------------------------------
// Serialize encodes a value using gob.
func (e GobEncoder) Serialize(src interface{}) ([]byte, error) {
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
if err := enc.Encode(src); err != nil {
return nil, cookieError{cause: err, typ: usageError}
}
return buf.Bytes(), nil
}
// Deserialize decodes a value using gob.
func (e GobEncoder) Deserialize(src []byte, dst interface{}) error {
dec := gob.NewDecoder(bytes.NewBuffer(src))
if err := dec.Decode(dst); err != nil {
return cookieError{cause: err, typ: decodeError}
}
return nil
}
// Serialize encodes a value using encoding/json.
func (e JSONEncoder) Serialize(src interface{}) ([]byte, error) {
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
if err := enc.Encode(src); err != nil {
return nil, cookieError{cause: err, typ: usageError}
}
return buf.Bytes(), nil
}
// Deserialize decodes a value using encoding/json.
func (e JSONEncoder) Deserialize(src []byte, dst interface{}) error {
dec := json.NewDecoder(bytes.NewReader(src))
if err := dec.Decode(dst); err != nil {
return cookieError{cause: err, typ: decodeError}
}
return nil
}
// Serialize passes a []byte through as-is.
func (e NopEncoder) Serialize(src interface{}) ([]byte, error) {
if b, ok := src.([]byte); ok {
return b, nil
}
return nil, errValueNotByte
}
// Deserialize passes a []byte through as-is.
func (e NopEncoder) Deserialize(src []byte, dst interface{}) error {
if dat, ok := dst.(*[]byte); ok {
*dat = src
return nil
}
return errValueNotBytePtr
}
// Encoding -------------------------------------------------------------------
// encode encodes a value using base64.
func encode(value []byte) []byte {
encoded := make([]byte, base64.URLEncoding.EncodedLen(len(value)))
base64.URLEncoding.Encode(encoded, value)
return encoded
}
// decode decodes a cookie using base64.
func decode(value []byte) ([]byte, error) {
decoded := make([]byte, base64.URLEncoding.DecodedLen(len(value)))
b, err := base64.URLEncoding.Decode(decoded, value)
if err != nil {
return nil, cookieError{cause: err, typ: decodeError, msg: "base64 decode failed"}
}
return decoded[:b], nil
}
// Helpers --------------------------------------------------------------------
// GenerateRandomKey creates a random key with the given length in bytes.
// On failure, returns nil.
//
// Callers should explicitly check for the possibility of a nil return, treat
// it as a failure of the system random number generator, and not continue.
func GenerateRandomKey(length int) []byte {
k := make([]byte, length)
if _, err := io.ReadFull(rand.Reader, k); err != nil {
return nil
}
return k
}
// CodecsFromPairs returns a slice of SecureCookie instances.
//
// It is a convenience function to create a list of codecs for key rotation. Note
// that the generated Codecs will have the default options applied: callers
// should iterate over each Codec and type-assert the underlying *SecureCookie to
// change these.
//
// Example:
//
// codecs := securecookie.CodecsFromPairs(
// []byte("new-hash-key"),
// []byte("new-block-key"),
// []byte("old-hash-key"),
// []byte("old-block-key"),
// )
//
// // Modify each instance.
// for _, s := range codecs {
// if cookie, ok := s.(*securecookie.SecureCookie); ok {
// cookie.MaxAge(86400 * 7)
// cookie.SetSerializer(securecookie.JSONEncoder{})
// cookie.HashFunc(sha512.New512_256)
// }
// }
//
func CodecsFromPairs(keyPairs ...[]byte) []Codec {
codecs := make([]Codec, len(keyPairs)/2+len(keyPairs)%2)
for i := 0; i < len(keyPairs); i += 2 {
var blockKey []byte
if i+1 < len(keyPairs) {
blockKey = keyPairs[i+1]
}
codecs[i/2] = New(keyPairs[i], blockKey)
}
return codecs
}
// EncodeMulti encodes a cookie value using a group of codecs.
//
// The codecs are tried in order. Multiple codecs are accepted to allow
// key rotation.
//
// On error, may return a MultiError.
func EncodeMulti(name string, value interface{}, codecs ...Codec) (string, error) {
if len(codecs) == 0 {
return "", errNoCodecs
}
var errors MultiError
for _, codec := range codecs {
encoded, err := codec.Encode(name, value)
if err == nil {
return encoded, nil
}
errors = append(errors, err)
}
return "", errors
}
// DecodeMulti decodes a cookie value using a group of codecs.
//
// The codecs are tried in order. Multiple codecs are accepted to allow
// key rotation.
//
// On error, may return a MultiError.
func DecodeMulti(name string, value string, dst interface{}, codecs ...Codec) error {
if len(codecs) == 0 {
return errNoCodecs
}
var errors MultiError
for _, codec := range codecs {
err := codec.Decode(name, value, dst)
if err == nil {
return nil
}
errors = append(errors, err)
}
return errors
}
// MultiError groups multiple errors.
type MultiError []error
func (m MultiError) IsUsage() bool { return m.any(func(e Error) bool { return e.IsUsage() }) }
func (m MultiError) IsDecode() bool { return m.any(func(e Error) bool { return e.IsDecode() }) }
func (m MultiError) IsInternal() bool { return m.any(func(e Error) bool { return e.IsInternal() }) }
// Cause returns nil for MultiError; there is no unique underlying cause in the
// general case.
//
// Note: we could conceivably return a non-nil Cause only when there is exactly
// one child error with a Cause. However, it would be brittle for client code
// to rely on the arity of causes inside a MultiError, so we have opted not to
// provide this functionality. Clients which really wish to access the Causes
// of the underlying errors are free to iterate through the errors themselves.
func (m MultiError) Cause() error { return nil }
func (m MultiError) Error() string {
s, n := "", 0
for _, e := range m {
if e != nil {
if n == 0 {
s = e.Error()
}
n++
}
}
switch n {
case 0:
return "(0 errors)"
case 1:
return s
case 2:
return s + " (and 1 other error)"
}
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
}
// any returns true if any element of m is an Error for which pred returns true.
func (m MultiError) any(pred func(Error) bool) bool {
for _, e := range m {
if ourErr, ok := e.(Error); ok && pred(ourErr) {
return true
}
}
return false
}

43
vendor/github.com/gorilla/sessions/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,43 @@
# This is the official list of gorilla/sessions authors for copyright purposes.
#
# Please keep the list sorted.
Ahmadreza Zibaei <ahmadrezazibaei@hotmail.com>
Anton Lindström <lindztr@gmail.com>
Brian Jones <mojobojo@gmail.com>
Collin Stedman <kronion@users.noreply.github.com>
Deniz Eren <dee.116@gmail.com>
Dmitry Chestnykh <dmitry@codingrobots.com>
Dustin Oprea <myselfasunder@gmail.com>
Egon Elbre <egonelbre@gmail.com>
enumappstore <appstore@enumapps.com>
Geofrey Ernest <geofreyernest@live.com>
Google LLC (https://opensource.google.com/)
Jerry Saravia <SaraviaJ@gmail.com>
Jonathan Gillham <jonathan.gillham@gamil.com>
Justin Clift <justin@postgresql.org>
Justin Hellings <justin.hellings@gmail.com>
Kamil Kisiel <kamil@kamilkisiel.net>
Keiji Yoshida <yoshida.keiji.84@gmail.com>
kliron <kliron@gmail.com>
Kshitij Saraogi <KshitijSaraogi@gmail.com>
Lauris BH <lauris@nix.lv>
Lukas Rist <glaslos@gmail.com>
Mark Dain <ancarda@users.noreply.github.com>
Matt Ho <matt.ho@gmail.com>
Matt Silverlock <matt@eatsleeprepeat.net>
Mattias Wadman <mattias.wadman@gmail.com>
Michael Schuett <michaeljs1990@gmail.com>
Michael Stapelberg <stapelberg@users.noreply.github.com>
Mirco Zeiss <mirco.zeiss@gmail.com>
moraes <rodrigo.moraes@gmail.com>
nvcnvn <nguyen@open-vn.org>
pappz <zoltan.pmail@gmail.com>
Pontus Leitzler <leitzler@users.noreply.github.com>
QuaSoft <info@quasoft.net>
rcadena <robert.cadena@gmail.com>
rodrigo moraes <rodrigo.moraes@gmail.com>
Shawn Smith <shawnpsmith@gmail.com>
Taylor Hurt <taylor.a.hurt@gmail.com>
Tortuoise <sanyasinp@gmail.com>
Vitor De Mario <vitordemario@gmail.com>

27
vendor/github.com/gorilla/sessions/LICENSE generated vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

90
vendor/github.com/gorilla/sessions/README.md generated vendored Normal file
View File

@ -0,0 +1,90 @@
# sessions
[![GoDoc](https://godoc.org/github.com/gorilla/sessions?status.svg)](https://godoc.org/github.com/gorilla/sessions) [![Build Status](https://travis-ci.org/gorilla/sessions.svg?branch=master)](https://travis-ci.org/gorilla/sessions)
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/sessions/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/sessions?badge)
gorilla/sessions provides cookie and filesystem sessions and infrastructure for
custom session backends.
The key features are:
- Simple API: use it as an easy way to set signed (and optionally
encrypted) cookies.
- Built-in backends to store sessions in cookies or the filesystem.
- Flash messages: session values that last until read.
- Convenient way to switch session persistency (aka "remember me") and set
other attributes.
- Mechanism to rotate authentication and encryption keys.
- Multiple sessions per request, even using different backends.
- Interfaces and infrastructure for custom session backends: sessions from
different stores can be retrieved and batch-saved using a common API.
Let's start with an example that shows the sessions API in a nutshell:
```go
import (
"net/http"
"github.com/gorilla/sessions"
)
// Note: Don't store your key in your source code. Pass it via an
// environmental variable, or flag (or both), and don't accidentally commit it
// alongside your code. Ensure your key is sufficiently random - i.e. use Go's
// crypto/rand or securecookie.GenerateRandomKey(32) and persist the result.
var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))
func MyHandler(w http.ResponseWriter, r *http.Request) {
// Get a session. We're ignoring the error resulted from decoding an
// existing session: Get() always returns a session, even if empty.
session, _ := store.Get(r, "session-name")
// Set some session values.
session.Values["foo"] = "bar"
session.Values[42] = 43
// Save it before we write to the response/return from the handler.
err := session.Save(r, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
```
First we initialize a session store calling `NewCookieStore()` and passing a
secret key used to authenticate the session. Inside the handler, we call
`store.Get()` to retrieve an existing session or create a new one. Then we set
some session values in session.Values, which is a `map[interface{}]interface{}`.
And finally we call `session.Save()` to save the session in the response.
More examples are available [on the Gorilla
website](https://www.gorillatoolkit.org/pkg/sessions).
## Store Implementations
Other implementations of the `sessions.Store` interface:
- [github.com/starJammer/gorilla-sessions-arangodb](https://github.com/starJammer/gorilla-sessions-arangodb) - ArangoDB
- [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Bolt
- [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase
- [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb on AWS
- [github.com/savaki/dynastore](https://github.com/savaki/dynastore) - DynamoDB on AWS (Official AWS library)
- [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache
- [github.com/dsoprea/go-appengine-sessioncascade](https://github.com/dsoprea/go-appengine-sessioncascade) - Memcache/Datastore/Context in AppEngine
- [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB
- [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL
- [github.com/EnumApps/clustersqlstore](https://github.com/EnumApps/clustersqlstore) - MySQL Cluster
- [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL
- [github.com/boj/redistore](https://github.com/boj/redistore) - Redis
- [github.com/rbcervilla/redisstore](https://github.com/rbcervilla/redisstore) - Redis (Single, Sentinel, Cluster)
- [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB
- [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak
- [github.com/michaeljs1990/sqlitestore](https://github.com/michaeljs1990/sqlitestore) - SQLite
- [github.com/wader/gormstore](https://github.com/wader/gormstore) - GORM (MySQL, PostgreSQL, SQLite)
- [github.com/gernest/qlstore](https://github.com/gernest/qlstore) - ql
- [github.com/quasoft/memstore](https://github.com/quasoft/memstore) - In-memory implementation for use in unit tests
- [github.com/lafriks/xormstore](https://github.com/lafriks/xormstore) - XORM (MySQL, PostgreSQL, SQLite, Microsoft SQL Server, TiDB)
- [github.com/GoogleCloudPlatform/firestore-gorilla-sessions](https://github.com/GoogleCloudPlatform/firestore-gorilla-sessions) - Cloud Firestore
- [github.com/stephenafamo/crdbstore](https://github.com/stephenafamo/crdbstore) - CockroachDB
## License
BSD licensed. See the LICENSE file for details.

19
vendor/github.com/gorilla/sessions/cookie.go generated vendored Normal file
View File

@ -0,0 +1,19 @@
// +build !go1.11
package sessions
import "net/http"
// newCookieFromOptions returns an http.Cookie with the options set.
func newCookieFromOptions(name, value string, options *Options) *http.Cookie {
return &http.Cookie{
Name: name,
Value: value,
Path: options.Path,
Domain: options.Domain,
MaxAge: options.MaxAge,
Secure: options.Secure,
HttpOnly: options.HttpOnly,
}
}

20
vendor/github.com/gorilla/sessions/cookie_go111.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
// +build go1.11
package sessions
import "net/http"
// newCookieFromOptions returns an http.Cookie with the options set.
func newCookieFromOptions(name, value string, options *Options) *http.Cookie {
return &http.Cookie{
Name: name,
Value: value,
Path: options.Path,
Domain: options.Domain,
MaxAge: options.MaxAge,
Secure: options.Secure,
HttpOnly: options.HttpOnly,
SameSite: options.SameSite,
}
}

207
vendor/github.com/gorilla/sessions/doc.go generated vendored Normal file
View File

@ -0,0 +1,207 @@
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package sessions provides cookie and filesystem sessions and
infrastructure for custom session backends.
The key features are:
* Simple API: use it as an easy way to set signed (and optionally
encrypted) cookies.
* Built-in backends to store sessions in cookies or the filesystem.
* Flash messages: session values that last until read.
* Convenient way to switch session persistency (aka "remember me") and set
other attributes.
* Mechanism to rotate authentication and encryption keys.
* Multiple sessions per request, even using different backends.
* Interfaces and infrastructure for custom session backends: sessions from
different stores can be retrieved and batch-saved using a common API.
Let's start with an example that shows the sessions API in a nutshell:
import (
"net/http"
"github.com/gorilla/sessions"
)
// Note: Don't store your key in your source code. Pass it via an
// environmental variable, or flag (or both), and don't accidentally commit it
// alongside your code. Ensure your key is sufficiently random - i.e. use Go's
// crypto/rand or securecookie.GenerateRandomKey(32) and persist the result.
// Ensure SESSION_KEY exists in the environment, or sessions will fail.
var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))
func MyHandler(w http.ResponseWriter, r *http.Request) {
// Get a session. Get() always returns a session, even if empty.
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Set some session values.
session.Values["foo"] = "bar"
session.Values[42] = 43
// Save it before we write to the response/return from the handler.
err = session.Save(r, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
First we initialize a session store calling NewCookieStore() and passing a
secret key used to authenticate the session. Inside the handler, we call
store.Get() to retrieve an existing session or a new one. Then we set some
session values in session.Values, which is a map[interface{}]interface{}.
And finally we call session.Save() to save the session in the response.
Note that in production code, we should check for errors when calling
session.Save(r, w), and either display an error message or otherwise handle it.
Save must be called before writing to the response, otherwise the session
cookie will not be sent to the client.
That's all you need to know for the basic usage. Let's take a look at other
options, starting with flash messages.
Flash messages are session values that last until read. The term appeared with
Ruby On Rails a few years back. When we request a flash message, it is removed
from the session. To add a flash, call session.AddFlash(), and to get all
flashes, call session.Flashes(). Here is an example:
func MyHandler(w http.ResponseWriter, r *http.Request) {
// Get a session.
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Get the previous flashes, if any.
if flashes := session.Flashes(); len(flashes) > 0 {
// Use the flash values.
} else {
// Set a new flash.
session.AddFlash("Hello, flash messages world!")
}
err = session.Save(r, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
Flash messages are useful to set information to be read after a redirection,
like after form submissions.
There may also be cases where you want to store a complex datatype within a
session, such as a struct. Sessions are serialised using the encoding/gob package,
so it is easy to register new datatypes for storage in sessions:
import(
"encoding/gob"
"github.com/gorilla/sessions"
)
type Person struct {
FirstName string
LastName string
Email string
Age int
}
type M map[string]interface{}
func init() {
gob.Register(&Person{})
gob.Register(&M{})
}
As it's not possible to pass a raw type as a parameter to a function, gob.Register()
relies on us passing it a value of the desired type. In the example above we've passed
it a pointer to a struct and a pointer to a custom type representing a
map[string]interface. (We could have passed non-pointer values if we wished.) This will
then allow us to serialise/deserialise values of those types to and from our sessions.
Note that because session values are stored in a map[string]interface{}, there's
a need to type-assert data when retrieving it. We'll use the Person struct we registered above:
func MyHandler(w http.ResponseWriter, r *http.Request) {
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Retrieve our struct and type-assert it
val := session.Values["person"]
var person = &Person{}
if person, ok := val.(*Person); !ok {
// Handle the case that it's not an expected type
}
// Now we can use our person object
}
By default, session cookies last for a month. This is probably too long for
some cases, but it is easy to change this and other attributes during
runtime. Sessions can be configured individually or the store can be
configured and then all sessions saved using it will use that configuration.
We access session.Options or store.Options to set a new configuration. The
fields are basically a subset of http.Cookie fields. Let's change the
maximum age of a session to one week:
session.Options = &sessions.Options{
Path: "/",
MaxAge: 86400 * 7,
HttpOnly: true,
}
Sometimes we may want to change authentication and/or encryption keys without
breaking existing sessions. The CookieStore supports key rotation, and to use
it you just need to set multiple authentication and encryption keys, in pairs,
to be tested in order:
var store = sessions.NewCookieStore(
[]byte("new-authentication-key"),
[]byte("new-encryption-key"),
[]byte("old-authentication-key"),
[]byte("old-encryption-key"),
)
New sessions will be saved using the first pair. Old sessions can still be
read because the first pair will fail, and the second will be tested. This
makes it easy to "rotate" secret keys and still be able to validate existing
sessions. Note: for all pairs the encryption key is optional; set it to nil
or omit it and and encryption won't be used.
Multiple sessions can be used in the same request, even with different
session backends. When this happens, calling Save() on each session
individually would be cumbersome, so we have a way to save all sessions
at once: it's sessions.Save(). Here's an example:
var store = sessions.NewCookieStore([]byte("something-very-secret"))
func MyHandler(w http.ResponseWriter, r *http.Request) {
// Get a session and set a value.
session1, _ := store.Get(r, "session-one")
session1.Values["foo"] = "bar"
// Get another session and set another value.
session2, _ := store.Get(r, "session-two")
session2.Values[42] = 43
// Save all sessions.
err = sessions.Save(r, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
This is possible because when we call Get() from a session store, it adds the
session to a common registry. Save() uses it to save all registered sessions.
*/
package sessions

102
vendor/github.com/gorilla/sessions/lex.go generated vendored Normal file
View File

@ -0,0 +1,102 @@
// This file contains code adapted from the Go standard library
// https://github.com/golang/go/blob/39ad0fd0789872f9469167be7fe9578625ff246e/src/net/http/lex.go
package sessions
import "strings"
var isTokenTable = [127]bool{
'!': true,
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
'*': true,
'+': true,
'-': true,
'.': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'W': true,
'V': true,
'X': true,
'Y': true,
'Z': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'|': true,
'~': true,
}
func isToken(r rune) bool {
i := int(r)
return i < len(isTokenTable) && isTokenTable[i]
}
func isNotToken(r rune) bool {
return !isToken(r)
}
func isCookieNameValid(raw string) bool {
if raw == "" {
return false
}
return strings.IndexFunc(raw, isNotToken) < 0
}

18
vendor/github.com/gorilla/sessions/options.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
// +build !go1.11
package sessions
// Options stores configuration for a session or session store.
//
// Fields are a subset of http.Cookie fields.
type Options struct {
Path string
Domain string
// MaxAge=0 means no Max-Age attribute specified and the cookie will be
// deleted after the browser session ends.
// MaxAge<0 means delete cookie immediately.
// MaxAge>0 means Max-Age attribute present and given in seconds.
MaxAge int
Secure bool
HttpOnly bool
}

22
vendor/github.com/gorilla/sessions/options_go111.go generated vendored Normal file
View File

@ -0,0 +1,22 @@
// +build go1.11
package sessions
import "net/http"
// Options stores configuration for a session or session store.
//
// Fields are a subset of http.Cookie fields.
type Options struct {
Path string
Domain string
// MaxAge=0 means no Max-Age attribute specified and the cookie will be
// deleted after the browser session ends.
// MaxAge<0 means delete cookie immediately.
// MaxAge>0 means Max-Age attribute present and given in seconds.
MaxAge int
Secure bool
HttpOnly bool
// Defaults to http.SameSiteDefaultMode
SameSite http.SameSite
}

218
vendor/github.com/gorilla/sessions/sessions.go generated vendored Normal file
View File

@ -0,0 +1,218 @@
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sessions
import (
"context"
"encoding/gob"
"fmt"
"net/http"
"time"
)
// Default flashes key.
const flashesKey = "_flash"
// Session --------------------------------------------------------------------
// NewSession is called by session stores to create a new session instance.
func NewSession(store Store, name string) *Session {
return &Session{
Values: make(map[interface{}]interface{}),
store: store,
name: name,
Options: new(Options),
}
}
// Session stores the values and optional configuration for a session.
type Session struct {
// The ID of the session, generated by stores. It should not be used for
// user data.
ID string
// Values contains the user-data for the session.
Values map[interface{}]interface{}
Options *Options
IsNew bool
store Store
name string
}
// Flashes returns a slice of flash messages from the session.
//
// A single variadic argument is accepted, and it is optional: it defines
// the flash key. If not defined "_flash" is used by default.
func (s *Session) Flashes(vars ...string) []interface{} {
var flashes []interface{}
key := flashesKey
if len(vars) > 0 {
key = vars[0]
}
if v, ok := s.Values[key]; ok {
// Drop the flashes and return it.
delete(s.Values, key)
flashes = v.([]interface{})
}
return flashes
}
// AddFlash adds a flash message to the session.
//
// A single variadic argument is accepted, and it is optional: it defines
// the flash key. If not defined "_flash" is used by default.
func (s *Session) AddFlash(value interface{}, vars ...string) {
key := flashesKey
if len(vars) > 0 {
key = vars[0]
}
var flashes []interface{}
if v, ok := s.Values[key]; ok {
flashes = v.([]interface{})
}
s.Values[key] = append(flashes, value)
}
// Save is a convenience method to save this session. It is the same as calling
// store.Save(request, response, session). You should call Save before writing to
// the response or returning from the handler.
func (s *Session) Save(r *http.Request, w http.ResponseWriter) error {
return s.store.Save(r, w, s)
}
// Name returns the name used to register the session.
func (s *Session) Name() string {
return s.name
}
// Store returns the session store used to register the session.
func (s *Session) Store() Store {
return s.store
}
// Registry -------------------------------------------------------------------
// sessionInfo stores a session tracked by the registry.
type sessionInfo struct {
s *Session
e error
}
// contextKey is the type used to store the registry in the context.
type contextKey int
// registryKey is the key used to store the registry in the context.
const registryKey contextKey = 0
// GetRegistry returns a registry instance for the current request.
func GetRegistry(r *http.Request) *Registry {
var ctx = r.Context()
registry := ctx.Value(registryKey)
if registry != nil {
return registry.(*Registry)
}
newRegistry := &Registry{
request: r,
sessions: make(map[string]sessionInfo),
}
*r = *r.WithContext(context.WithValue(ctx, registryKey, newRegistry))
return newRegistry
}
// Registry stores sessions used during a request.
type Registry struct {
request *http.Request
sessions map[string]sessionInfo
}
// Get registers and returns a session for the given name and session store.
//
// It returns a new session if there are no sessions registered for the name.
func (s *Registry) Get(store Store, name string) (session *Session, err error) {
if !isCookieNameValid(name) {
return nil, fmt.Errorf("sessions: invalid character in cookie name: %s", name)
}
if info, ok := s.sessions[name]; ok {
session, err = info.s, info.e
} else {
session, err = store.New(s.request, name)
session.name = name
s.sessions[name] = sessionInfo{s: session, e: err}
}
session.store = store
return
}
// Save saves all sessions registered for the current request.
func (s *Registry) Save(w http.ResponseWriter) error {
var errMulti MultiError
for name, info := range s.sessions {
session := info.s
if session.store == nil {
errMulti = append(errMulti, fmt.Errorf(
"sessions: missing store for session %q", name))
} else if err := session.store.Save(s.request, w, session); err != nil {
errMulti = append(errMulti, fmt.Errorf(
"sessions: error saving session %q -- %v", name, err))
}
}
if errMulti != nil {
return errMulti
}
return nil
}
// Helpers --------------------------------------------------------------------
func init() {
gob.Register([]interface{}{})
}
// Save saves all sessions used during the current request.
func Save(r *http.Request, w http.ResponseWriter) error {
return GetRegistry(r).Save(w)
}
// NewCookie returns an http.Cookie with the options set. It also sets
// the Expires field calculated based on the MaxAge value, for Internet
// Explorer compatibility.
func NewCookie(name, value string, options *Options) *http.Cookie {
cookie := newCookieFromOptions(name, value, options)
if options.MaxAge > 0 {
d := time.Duration(options.MaxAge) * time.Second
cookie.Expires = time.Now().Add(d)
} else if options.MaxAge < 0 {
// Set it to the past to expire now.
cookie.Expires = time.Unix(1, 0)
}
return cookie
}
// Error ----------------------------------------------------------------------
// MultiError stores multiple errors.
//
// Borrowed from the App Engine SDK.
type MultiError []error
func (m MultiError) Error() string {
s, n := "", 0
for _, e := range m {
if e != nil {
if n == 0 {
s = e.Error()
}
n++
}
}
switch n {
case 0:
return "(0 errors)"
case 1:
return s
case 2:
return s + " (and 1 other error)"
}
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
}

292
vendor/github.com/gorilla/sessions/store.go generated vendored Normal file
View File

@ -0,0 +1,292 @@
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sessions
import (
"encoding/base32"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"github.com/gorilla/securecookie"
)
// Store is an interface for custom session stores.
//
// See CookieStore and FilesystemStore for examples.
type Store interface {
// Get should return a cached session.
Get(r *http.Request, name string) (*Session, error)
// New should create and return a new session.
//
// Note that New should never return a nil session, even in the case of
// an error if using the Registry infrastructure to cache the session.
New(r *http.Request, name string) (*Session, error)
// Save should persist session to the underlying store implementation.
Save(r *http.Request, w http.ResponseWriter, s *Session) error
}
// CookieStore ----------------------------------------------------------------
// NewCookieStore returns a new CookieStore.
//
// Keys are defined in pairs to allow key rotation, but the common case is
// to set a single authentication key and optionally an encryption key.
//
// The first key in a pair is used for authentication and the second for
// encryption. The encryption key can be set to nil or omitted in the last
// pair, but the authentication key is required in all pairs.
//
// It is recommended to use an authentication key with 32 or 64 bytes.
// The encryption key, if set, must be either 16, 24, or 32 bytes to select
// AES-128, AES-192, or AES-256 modes.
func NewCookieStore(keyPairs ...[]byte) *CookieStore {
cs := &CookieStore{
Codecs: securecookie.CodecsFromPairs(keyPairs...),
Options: &Options{
Path: "/",
MaxAge: 86400 * 30,
},
}
cs.MaxAge(cs.Options.MaxAge)
return cs
}
// CookieStore stores sessions using secure cookies.
type CookieStore struct {
Codecs []securecookie.Codec
Options *Options // default configuration
}
// Get returns a session for the given name after adding it to the registry.
//
// It returns a new session if the sessions doesn't exist. Access IsNew on
// the session to check if it is an existing session or a new one.
//
// It returns a new session and an error if the session exists but could
// not be decoded.
func (s *CookieStore) Get(r *http.Request, name string) (*Session, error) {
return GetRegistry(r).Get(s, name)
}
// New returns a session for the given name without adding it to the registry.
//
// The difference between New() and Get() is that calling New() twice will
// decode the session data twice, while Get() registers and reuses the same
// decoded session after the first call.
func (s *CookieStore) New(r *http.Request, name string) (*Session, error) {
session := NewSession(s, name)
opts := *s.Options
session.Options = &opts
session.IsNew = true
var err error
if c, errCookie := r.Cookie(name); errCookie == nil {
err = securecookie.DecodeMulti(name, c.Value, &session.Values,
s.Codecs...)
if err == nil {
session.IsNew = false
}
}
return session, err
}
// Save adds a single session to the response.
func (s *CookieStore) Save(r *http.Request, w http.ResponseWriter,
session *Session) error {
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
s.Codecs...)
if err != nil {
return err
}
http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
return nil
}
// MaxAge sets the maximum age for the store and the underlying cookie
// implementation. Individual sessions can be deleted by setting Options.MaxAge
// = -1 for that session.
func (s *CookieStore) MaxAge(age int) {
s.Options.MaxAge = age
// Set the maxAge for each securecookie instance.
for _, codec := range s.Codecs {
if sc, ok := codec.(*securecookie.SecureCookie); ok {
sc.MaxAge(age)
}
}
}
// FilesystemStore ------------------------------------------------------------
var fileMutex sync.RWMutex
// NewFilesystemStore returns a new FilesystemStore.
//
// The path argument is the directory where sessions will be saved. If empty
// it will use os.TempDir().
//
// See NewCookieStore() for a description of the other parameters.
func NewFilesystemStore(path string, keyPairs ...[]byte) *FilesystemStore {
if path == "" {
path = os.TempDir()
}
fs := &FilesystemStore{
Codecs: securecookie.CodecsFromPairs(keyPairs...),
Options: &Options{
Path: "/",
MaxAge: 86400 * 30,
},
path: path,
}
fs.MaxAge(fs.Options.MaxAge)
return fs
}
// FilesystemStore stores sessions in the filesystem.
//
// It also serves as a reference for custom stores.
//
// This store is still experimental and not well tested. Feedback is welcome.
type FilesystemStore struct {
Codecs []securecookie.Codec
Options *Options // default configuration
path string
}
// MaxLength restricts the maximum length of new sessions to l.
// If l is 0 there is no limit to the size of a session, use with caution.
// The default for a new FilesystemStore is 4096.
func (s *FilesystemStore) MaxLength(l int) {
for _, c := range s.Codecs {
if codec, ok := c.(*securecookie.SecureCookie); ok {
codec.MaxLength(l)
}
}
}
// Get returns a session for the given name after adding it to the registry.
//
// See CookieStore.Get().
func (s *FilesystemStore) Get(r *http.Request, name string) (*Session, error) {
return GetRegistry(r).Get(s, name)
}
// New returns a session for the given name without adding it to the registry.
//
// See CookieStore.New().
func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) {
session := NewSession(s, name)
opts := *s.Options
session.Options = &opts
session.IsNew = true
var err error
if c, errCookie := r.Cookie(name); errCookie == nil {
err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
if err == nil {
err = s.load(session)
if err == nil {
session.IsNew = false
}
}
}
return session, err
}
// Save adds a single session to the response.
//
// If the Options.MaxAge of the session is <= 0 then the session file will be
// deleted from the store path. With this process it enforces the properly
// session cookie handling so no need to trust in the cookie management in the
// web browser.
func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter,
session *Session) error {
// Delete if max-age is <= 0
if session.Options.MaxAge <= 0 {
if err := s.erase(session); err != nil {
return err
}
http.SetCookie(w, NewCookie(session.Name(), "", session.Options))
return nil
}
if session.ID == "" {
// Because the ID is used in the filename, encode it to
// use alphanumeric characters only.
session.ID = strings.TrimRight(
base32.StdEncoding.EncodeToString(
securecookie.GenerateRandomKey(32)), "=")
}
if err := s.save(session); err != nil {
return err
}
encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
s.Codecs...)
if err != nil {
return err
}
http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
return nil
}
// MaxAge sets the maximum age for the store and the underlying cookie
// implementation. Individual sessions can be deleted by setting Options.MaxAge
// = -1 for that session.
func (s *FilesystemStore) MaxAge(age int) {
s.Options.MaxAge = age
// Set the maxAge for each securecookie instance.
for _, codec := range s.Codecs {
if sc, ok := codec.(*securecookie.SecureCookie); ok {
sc.MaxAge(age)
}
}
}
// save writes encoded session.Values to a file.
func (s *FilesystemStore) save(session *Session) error {
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
s.Codecs...)
if err != nil {
return err
}
filename := filepath.Join(s.path, "session_"+session.ID)
fileMutex.Lock()
defer fileMutex.Unlock()
return ioutil.WriteFile(filename, []byte(encoded), 0600)
}
// load reads a file and decodes its content into session.Values.
func (s *FilesystemStore) load(session *Session) error {
filename := filepath.Join(s.path, "session_"+session.ID)
fileMutex.RLock()
defer fileMutex.RUnlock()
fdata, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
if err = securecookie.DecodeMulti(session.Name(), string(fdata),
&session.Values, s.Codecs...); err != nil {
return err
}
return nil
}
// delete session file
func (s *FilesystemStore) erase(session *Session) error {
filename := filepath.Join(s.path, "session_"+session.ID)
fileMutex.RLock()
defer fileMutex.RUnlock()
err := os.Remove(filename)
return err
}

6
vendor/modules.txt vendored
View File

@ -351,6 +351,12 @@ github.com/google/gopacket/routing
# github.com/google/uuid v1.3.0
## explicit
github.com/google/uuid
# github.com/gorilla/securecookie v1.1.1
## explicit
github.com/gorilla/securecookie
# github.com/gorilla/sessions v1.2.1
## explicit
github.com/gorilla/sessions
# github.com/gorilla/websocket v1.5.0
## explicit; go 1.12
github.com/gorilla/websocket