You've already forked oauth2-proxy
mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-06-23 00:40:46 +02:00
Centralize Ticket management of persistent stores (#682)
* Centralize Ticket management of persistent stores persistence package with Manager & Ticket will handle all the details about keys, secrets, ticket into cookies, etc. Persistent stores just need to pass Save, Load & Clear function handles to the persistent manager now. * Shift to persistence.Manager wrapping a persistence.Store * Break up the Redis client builder logic * Move error messages to Store from Manager * Convert ticket to private for Manager use only * Add persistence Manager & ticket tests * Make a custom MockStore that handles time FastForwards
This commit is contained in:
15
pkg/sessions/persistence/interfaces.go
Normal file
15
pkg/sessions/persistence/interfaces.go
Normal file
@ -0,0 +1,15 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Store is used for persistent session stores (IE not Cookie)
|
||||
// Implementing this interface allows it to easily use the persistence.Manager
|
||||
// for session ticket + encryption details.
|
||||
type Store interface {
|
||||
Save(context.Context, string, []byte, time.Duration) error
|
||||
Load(context.Context, string) ([]byte, error)
|
||||
Clear(context.Context, string) error
|
||||
}
|
91
pkg/sessions/persistence/manager.go
Normal file
91
pkg/sessions/persistence/manager.go
Normal file
@ -0,0 +1,91 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
|
||||
)
|
||||
|
||||
// Manager wraps a Store and handles the implementation details of the
|
||||
// sessions.SessionStore with its use of session tickets
|
||||
type Manager struct {
|
||||
Store Store
|
||||
Options *options.Cookie
|
||||
}
|
||||
|
||||
// NewManager creates a Manager that can wrap a Store and manage the
|
||||
// sessions.SessionStore implementation details
|
||||
func NewManager(store Store, cookieOpts *options.Cookie) *Manager {
|
||||
return &Manager{
|
||||
Store: store,
|
||||
Options: cookieOpts,
|
||||
}
|
||||
}
|
||||
|
||||
// Save saves a session in a persistent Store. Save will generate (or reuse an
|
||||
// existing) ticket which manages unique per session encryption & retrieval
|
||||
// from the persistent data store.
|
||||
func (m *Manager) Save(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error {
|
||||
if s.CreatedAt == nil || s.CreatedAt.IsZero() {
|
||||
now := time.Now()
|
||||
s.CreatedAt = &now
|
||||
}
|
||||
|
||||
tckt, err := decodeTicketFromRequest(req, m.Options)
|
||||
if err != nil {
|
||||
tckt, err = newTicket(m.Options)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating a session ticket: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = tckt.saveSession(s, func(key string, val []byte, exp time.Duration) error {
|
||||
return m.Store.Save(req.Context(), key, val, exp)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tckt.setCookie(rw, req, s)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load reads sessions.SessionState information from a session store. It will
|
||||
// use the session ticket from the http.Request's cookie.
|
||||
func (m *Manager) Load(req *http.Request) (*sessions.SessionState, error) {
|
||||
tckt, err := decodeTicketFromRequest(req, m.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tckt.loadSession(func(key string) ([]byte, error) {
|
||||
return m.Store.Load(req.Context(), key)
|
||||
})
|
||||
}
|
||||
|
||||
// Clear clears any saved session information for a given ticket cookie.
|
||||
// Then it clears all session data for that ticket in the Store.
|
||||
func (m *Manager) Clear(rw http.ResponseWriter, req *http.Request) error {
|
||||
tckt, err := decodeTicketFromRequest(req, m.Options)
|
||||
if err != nil {
|
||||
// Always clear the cookie, even when we can't load a cookie from
|
||||
// the request
|
||||
tckt = &ticket{
|
||||
options: m.Options,
|
||||
}
|
||||
tckt.clearCookie(rw, req)
|
||||
// Don't raise an error if we didn't have a Cookie
|
||||
if err == http.ErrNoCookie {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("error decoding ticket to clear session: %v", err)
|
||||
}
|
||||
|
||||
tckt.clearCookie(rw, req)
|
||||
return tckt.clearSession(func(key string) error {
|
||||
return m.Store.Clear(req.Context(), key)
|
||||
})
|
||||
}
|
34
pkg/sessions/persistence/manager_test.go
Normal file
34
pkg/sessions/persistence/manager_test.go
Normal file
@ -0,0 +1,34 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/tests"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestManager(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Persistence Manager SessionStore")
|
||||
}
|
||||
|
||||
var _ = Describe("Persistence Manager SessionStore Tests", func() {
|
||||
var ms *tests.MockStore
|
||||
BeforeEach(func() {
|
||||
ms = tests.NewMockStore()
|
||||
})
|
||||
tests.RunSessionStoreTests(
|
||||
func(_ *options.SessionOptions, cookieOpts *options.Cookie) (sessionsapi.SessionStore, error) {
|
||||
return NewManager(ms, cookieOpts), nil
|
||||
},
|
||||
func(d time.Duration) error {
|
||||
ms.FastForward(d)
|
||||
return nil
|
||||
})
|
||||
})
|
221
pkg/sessions/persistence/ticket.go
Normal file
221
pkg/sessions/persistence/ticket.go
Normal file
@ -0,0 +1,221 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/cookies"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
|
||||
)
|
||||
|
||||
// saveFunc performs a persistent store's save functionality using
|
||||
// a key string, value []byte & (optional) expiration time.Duration
|
||||
type saveFunc func(string, []byte, time.Duration) error
|
||||
|
||||
// loadFunc performs a load from a persistent store using a
|
||||
// string key and returning the stored value as []byte
|
||||
type loadFunc func(string) ([]byte, error)
|
||||
|
||||
// clearFunc performs a persistent store's clear functionality using
|
||||
// a string key for the target of the deletion.
|
||||
type clearFunc func(string) error
|
||||
|
||||
// ticket is a structure representing the ticket used in server based
|
||||
// session storage. It provides a unique per session decryption secret giving
|
||||
// more security than the shared CookieSecret.
|
||||
type ticket struct {
|
||||
id string
|
||||
secret []byte
|
||||
options *options.Cookie
|
||||
}
|
||||
|
||||
// newTicket creates a new ticket. The ID & secret will be randomly created
|
||||
// with 16 byte sizes. The ID will be prefixed & hex encoded.
|
||||
func newTicket(cookieOpts *options.Cookie) (*ticket, error) {
|
||||
rawID := make([]byte, 16)
|
||||
if _, err := io.ReadFull(rand.Reader, rawID); err != nil {
|
||||
return nil, fmt.Errorf("failed to create new ticket ID: %v", err)
|
||||
}
|
||||
// ticketID is hex encoded
|
||||
ticketID := fmt.Sprintf("%s-%s", cookieOpts.Name, hex.EncodeToString(rawID))
|
||||
|
||||
secret := make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, secret); err != nil {
|
||||
return nil, fmt.Errorf("failed to create encryption secret: %v", err)
|
||||
}
|
||||
|
||||
return &ticket{
|
||||
id: ticketID,
|
||||
secret: secret,
|
||||
options: cookieOpts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// encodeTicket encodes the Ticket to a string for usage in cookies
|
||||
func (t *ticket) encodeTicket() string {
|
||||
return fmt.Sprintf("%s.%s", t.id, base64.RawURLEncoding.EncodeToString(t.secret))
|
||||
}
|
||||
|
||||
// decodeTicket decodes an encoded ticket string
|
||||
func decodeTicket(encTicket string, cookieOpts *options.Cookie) (*ticket, error) {
|
||||
ticketParts := strings.Split(encTicket, ".")
|
||||
if len(ticketParts) != 2 {
|
||||
return nil, errors.New("failed to decode ticket")
|
||||
}
|
||||
ticketID, secretBase64 := ticketParts[0], ticketParts[1]
|
||||
|
||||
secret, err := base64.RawURLEncoding.DecodeString(secretBase64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode encryption secret: %v", err)
|
||||
}
|
||||
|
||||
return &ticket{
|
||||
id: ticketID,
|
||||
secret: secret,
|
||||
options: cookieOpts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// decodeTicketFromRequest retrieves a potential ticket cookie from a request
|
||||
// and decodes it to a ticket.
|
||||
func decodeTicketFromRequest(req *http.Request, cookieOpts *options.Cookie) (*ticket, error) {
|
||||
requestCookie, err := req.Cookie(cookieOpts.Name)
|
||||
if err != nil {
|
||||
// Don't wrap this error to allow `err == http.ErrNoCookie` checks
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// An existing cookie exists, try to retrieve the ticket
|
||||
val, _, ok := encryption.Validate(requestCookie, cookieOpts.Secret, cookieOpts.Expire)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("session ticket cookie failed validation: %v", err)
|
||||
}
|
||||
|
||||
// Valid cookie, decode the ticket
|
||||
return decodeTicket(string(val), cookieOpts)
|
||||
}
|
||||
|
||||
// saveSession encodes the SessionState with the ticket's secret and persists
|
||||
// it to disk via the passed saveFunc.
|
||||
func (t *ticket) saveSession(s *sessions.SessionState, saver saveFunc) error {
|
||||
c, err := t.makeCipher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ciphertext, err := s.EncodeSessionState(c, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode the session state with the ticket: %v", err)
|
||||
}
|
||||
return saver(t.id, ciphertext, t.options.Expire)
|
||||
}
|
||||
|
||||
// loadSession loads a session from the disk store via the passed loadFunc
|
||||
// using the ticket.id as the key. It then decodes the SessionState using
|
||||
// ticket.secret to make the AES-GCM cipher.
|
||||
//
|
||||
// TODO (@NickMeves): Remove legacyV5LoadSession support in V7
|
||||
func (t *ticket) loadSession(loader loadFunc) (*sessions.SessionState, error) {
|
||||
ciphertext, err := loader(t.id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load the session state with the ticket: %v", err)
|
||||
}
|
||||
c, err := t.makeCipher()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ss, err := sessions.DecodeSessionState(ciphertext, c, false)
|
||||
if err != nil {
|
||||
return t.legacyV5LoadSession(ciphertext)
|
||||
}
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
// clearSession uses the passed clearFunc to delete a session stored with a
|
||||
// key of ticket.id
|
||||
func (t *ticket) clearSession(clearer clearFunc) error {
|
||||
return clearer(t.id)
|
||||
}
|
||||
|
||||
// setCookie sets the encoded ticket as a cookie
|
||||
func (t *ticket) setCookie(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) {
|
||||
ticketCookie := t.makeCookie(
|
||||
req,
|
||||
t.encodeTicket(),
|
||||
t.options.Expire,
|
||||
*s.CreatedAt,
|
||||
)
|
||||
|
||||
http.SetCookie(rw, ticketCookie)
|
||||
}
|
||||
|
||||
// clearCookie removes any cookies that would be where this ticket
|
||||
// would set them
|
||||
func (t *ticket) clearCookie(rw http.ResponseWriter, req *http.Request) {
|
||||
clearCookie := t.makeCookie(
|
||||
req,
|
||||
"",
|
||||
time.Hour*-1,
|
||||
time.Now(),
|
||||
)
|
||||
http.SetCookie(rw, clearCookie)
|
||||
}
|
||||
|
||||
// makeCookie makes a cookie, signing the value if present
|
||||
func (t *ticket) makeCookie(req *http.Request, value string, expires time.Duration, now time.Time) *http.Cookie {
|
||||
if value != "" {
|
||||
value = encryption.SignedValue(t.options.Secret, t.options.Name, []byte(value), now)
|
||||
}
|
||||
return cookies.MakeCookieFromOptions(
|
||||
req,
|
||||
t.options.Name,
|
||||
value,
|
||||
t.options,
|
||||
expires,
|
||||
now,
|
||||
)
|
||||
}
|
||||
|
||||
// makeCipher makes a AES-GCM cipher out of the ticket's secret
|
||||
func (t *ticket) makeCipher() (encryption.Cipher, error) {
|
||||
c, err := encryption.NewGCMCipher(t.secret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to make an AES-GCM cipher from the ticket secret: %v", err)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// legacyV5LoadSession loads a Redis session created in V5 with historical logic
|
||||
//
|
||||
// TODO (@NickMeves): Remove in V7
|
||||
func (t *ticket) legacyV5LoadSession(resultBytes []byte) (*sessions.SessionState, error) {
|
||||
block, err := aes.NewCipher(t.secret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create a legacy AES-CFB cipher from the ticket secret: %v", err)
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBDecrypter(block, t.secret)
|
||||
stream.XORKeyStream(resultBytes, resultBytes)
|
||||
|
||||
cfbCipher, err := encryption.NewCFBCipher(encryption.SecretBytes(t.options.Secret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
legacyCipher := encryption.NewBase64Cipher(cfbCipher)
|
||||
|
||||
session, err := sessions.LegacyV5DecodeSessionState(string(resultBytes), legacyCipher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return session, nil
|
||||
}
|
223
pkg/sessions/persistence/ticket_test.go
Normal file
223
pkg/sessions/persistence/ticket_test.go
Normal file
@ -0,0 +1,223 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func Test_ticket(t *testing.T) {
|
||||
logger.SetOutput(GinkgoWriter)
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Session Ticket")
|
||||
}
|
||||
|
||||
var _ = Describe("Session Ticket Tests", func() {
|
||||
Context("encodeTicket & decodeTicket", func() {
|
||||
type ticketTableInput struct {
|
||||
ticket *ticket
|
||||
encodedTicket string
|
||||
expectedError error
|
||||
}
|
||||
|
||||
DescribeTable("encodeTicket should decodeTicket back when valid",
|
||||
func(in ticketTableInput) {
|
||||
if in.ticket != nil {
|
||||
enc := in.ticket.encodeTicket()
|
||||
Expect(enc).To(Equal(in.encodedTicket))
|
||||
|
||||
dec, err := decodeTicket(enc, in.ticket.options)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(dec).To(Equal(in.ticket))
|
||||
} else {
|
||||
_, err := decodeTicket(in.encodedTicket, nil)
|
||||
Expect(err).To(MatchError(in.expectedError))
|
||||
}
|
||||
},
|
||||
Entry("with a valid ticket", ticketTableInput{
|
||||
ticket: &ticket{
|
||||
id: "dummy-0123456789abcdef",
|
||||
secret: []byte("0123456789abcdef"),
|
||||
options: &options.Cookie{
|
||||
Name: "dummy",
|
||||
},
|
||||
},
|
||||
encodedTicket: fmt.Sprintf("%s.%s",
|
||||
"dummy-0123456789abcdef",
|
||||
base64.RawURLEncoding.EncodeToString([]byte("0123456789abcdef"))),
|
||||
expectedError: nil,
|
||||
}),
|
||||
Entry("with an invalid encoded ticket with 1 part", ticketTableInput{
|
||||
ticket: nil,
|
||||
encodedTicket: "dummy-0123456789abcdef",
|
||||
expectedError: errors.New("failed to decode ticket"),
|
||||
}),
|
||||
Entry("with an invalid base64 encoded secret", ticketTableInput{
|
||||
ticket: nil,
|
||||
encodedTicket: "dummy-0123456789abcdef.@)#($*@)#(*$@)#(*$",
|
||||
expectedError: fmt.Errorf("failed to decode encryption secret: illegal base64 data at input byte 0"),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
Context("saveSession", func() {
|
||||
It("uses the passed save function", func() {
|
||||
t, err := newTicket(&options.Cookie{Name: "dummy"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
c, err := t.makeCipher()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
ss := &sessions.SessionState{User: "foobar"}
|
||||
store := map[string][]byte{}
|
||||
err = t.saveSession(ss, func(k string, v []byte, e time.Duration) error {
|
||||
store[k] = v
|
||||
return nil
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
stored, err := sessions.DecodeSessionState(store[t.id], c, false)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(stored).To(Equal(ss))
|
||||
})
|
||||
|
||||
It("errors when the saveFunc errors", func() {
|
||||
t, err := newTicket(&options.Cookie{Name: "dummy"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = t.saveSession(
|
||||
&sessions.SessionState{User: "foobar"},
|
||||
func(k string, v []byte, e time.Duration) error {
|
||||
return errors.New("save error")
|
||||
})
|
||||
Expect(err).To(MatchError(errors.New("save error")))
|
||||
})
|
||||
})
|
||||
|
||||
Context("loadSession", func() {
|
||||
It("uses the passed load function", func() {
|
||||
t, err := newTicket(&options.Cookie{Name: "dummy"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
c, err := t.makeCipher()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
ss := &sessions.SessionState{User: "foobar"}
|
||||
loadedSession, err := t.loadSession(func(k string) ([]byte, error) {
|
||||
return ss.EncodeSessionState(c, false)
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(loadedSession).To(Equal(ss))
|
||||
})
|
||||
|
||||
It("errors when the loadFunc errors", func() {
|
||||
t, err := newTicket(&options.Cookie{Name: "dummy"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
data, err := t.loadSession(func(k string) ([]byte, error) {
|
||||
return nil, errors.New("load error")
|
||||
})
|
||||
Expect(data).To(BeNil())
|
||||
Expect(err).To(MatchError(errors.New("failed to load the session state with the ticket: load error")))
|
||||
})
|
||||
})
|
||||
|
||||
Context("clearSession", func() {
|
||||
It("uses the passed clear function", func() {
|
||||
t, err := newTicket(&options.Cookie{Name: "dummy"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
var tracker string
|
||||
err = t.clearSession(func(k string) error {
|
||||
tracker = k
|
||||
return nil
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(tracker).To(Equal(t.id))
|
||||
})
|
||||
|
||||
It("errors when the clearFunc errors", func() {
|
||||
t, err := newTicket(&options.Cookie{Name: "dummy"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = t.clearSession(func(k string) error {
|
||||
return errors.New("clear error")
|
||||
})
|
||||
Expect(err).To(MatchError(errors.New("clear error")))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// TestLegacyV5DecodeSession tests the fallback to LegacyV5DecodeSession
|
||||
// when a V5 encoded session is in Redis
|
||||
//
|
||||
// TODO (@NickMeves): Remove when this is deprecated (likely V7)
|
||||
func Test_legacyV5LoadSession(t *testing.T) {
|
||||
testCases, _, _ := sessions.CreateLegacyV5TestCases(t)
|
||||
|
||||
for testName, tc := range testCases {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
secret := make([]byte, aes.BlockSize)
|
||||
_, err := io.ReadFull(rand.Reader, secret)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
tckt := &ticket{
|
||||
secret: secret,
|
||||
options: &options.Cookie{
|
||||
Secret: base64.RawURLEncoding.EncodeToString([]byte(sessions.LegacyV5TestSecret)),
|
||||
},
|
||||
}
|
||||
|
||||
encrypted, err := legacyStoreValue(tc.Input, tckt.secret)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
ss, err := tckt.legacyV5LoadSession(encrypted)
|
||||
if tc.Error {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(ss).To(BeNil())
|
||||
return
|
||||
}
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Compare sessions without *time.Time fields
|
||||
exp := *tc.Output
|
||||
exp.CreatedAt = nil
|
||||
exp.ExpiresOn = nil
|
||||
act := *ss
|
||||
act.CreatedAt = nil
|
||||
act.ExpiresOn = nil
|
||||
g.Expect(exp).To(Equal(act))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// legacyStoreValue implements the legacy V5 Redis persistence AES-CFB value encryption
|
||||
//
|
||||
// TODO (@NickMeves): Remove when this is deprecated (likely V7)
|
||||
func legacyStoreValue(value string, ticketSecret []byte) ([]byte, error) {
|
||||
ciphertext := make([]byte, len(value))
|
||||
block, err := aes.NewCipher(ticketSecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error initiating cipher block: %v", err)
|
||||
}
|
||||
|
||||
// Use secret as the Initialization Vector too, because each entry has it's own key
|
||||
stream := cipher.NewCFBEncrypter(block, ticketSecret)
|
||||
stream.XORKeyStream(ciphertext, []byte(value))
|
||||
|
||||
return ciphertext, nil
|
||||
}
|
Reference in New Issue
Block a user