git.iko.soy/typeable/oauth2-proxy
on master help sign in
Implement LoadSession in Cookie SessionStore
commit8b3a3853eba73e41584ac48c5493aef1d409f25b authorJoel Speed <Joel.speed@hotmail.co.uk> dateMon 6 May 2019, 23:16 (7 years ago) parent965d95f
2 files changed+143−6
pkg/sessions/cookie/session_store.go+102−6 pkg/sessions/utils/utils.go+41
pkg/sessions/cookie/session_store.go+102−6
@@ -1,11 +1,16 @@
11package cookie
22
33import (
4+ "errors"
45 "fmt"
56 "net/http"
7+ "strings"
8+ "time"
69
10+ "github.com/pusher/oauth2_proxy/cookie"
711 "github.com/pusher/oauth2_proxy/pkg/apis/options"
812 "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
13+ "github.com/pusher/oauth2_proxy/pkg/sessions/utils"
914)
1015
1116// Ensure CookieSessionStore implements the interface
@@ -13,28 +18,119 @@ var _ sessions.SessionStore = &SessionStore{}
1318
1419// SessionStore is an implementation of the sessions.SessionStore
1520// interface that stores sessions in client side cookies
16−type SessionStore struct{}
21+type SessionStore struct {
22+ CookieCipher *cookie.Cipher
23+ CookieExpire time.Duration
24+ CookieName string
25+ CookieSecret string
26+}
1727
1828// SaveSession takes a sessions.SessionState and stores the information from it
1929// within Cookies set on the HTTP response writer
20−func (c *SessionStore) SaveSession(rw http.ResponseWriter, req *http.Request, s *sessions.SessionState) error {
30+func (s *SessionStore) SaveSession(rw http.ResponseWriter, req *http.Request, ss *sessions.SessionState) error {
2131 return fmt.Errorf("method not implemented")
2232}
2333
2434// LoadSession reads sessions.SessionState information from Cookies within the
2535// HTTP request object
26−func (c *SessionStore) LoadSession(req *http.Request) (*sessions.SessionState, error) {
27− return nil, fmt.Errorf("method not implemented")
36+func (s *SessionStore) LoadSession(req *http.Request) (*sessions.SessionState, error) {
37+ c, err := loadCookie(req, s.CookieName)
38+ if err != nil {
39+ // always http.ErrNoCookie
40+ return nil, fmt.Errorf("Cookie %q not present", s.CookieName)
41+ }
42+ val, _, ok := cookie.Validate(c, s.CookieSecret, s.CookieExpire)
43+ if !ok {
44+ return nil, errors.New("Cookie Signature not valid")
45+ }
46+
47+ session, err := utils.SessionFromCookie(val, s.CookieCipher)
48+ if err != nil {
49+ return nil, err
50+ }
51+ return session, nil
2852}
2953
3054// ClearSession clears any saved session information by writing a cookie to
3155// clear the session
32−func (c *SessionStore) ClearSession(rw http.ResponseWriter, req *http.Request) error {
56+func (s *SessionStore) ClearSession(rw http.ResponseWriter, req *http.Request) error {
3357 return fmt.Errorf("method not implemented")
3458}
3559
3660// NewCookieSessionStore initialises a new instance of the SessionStore from
3761// the configuration given
3862func NewCookieSessionStore(opts options.CookieStoreOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) {
39− return &SessionStore{}, fmt.Errorf("method not implemented")
63+ var cipher *cookie.Cipher
64+ if len(cookieOpts.CookieSecret) > 0 {
65+ var err error
66+ cipher, err = cookie.NewCipher(utils.SecretBytes(cookieOpts.CookieSecret))
67+ if err != nil {
68+ return nil, fmt.Errorf("unable to create cipher: %v", err)
69+ }
70+ }
71+
72+ return &SessionStore{
73+ CookieCipher: cipher,
74+ CookieExpire: cookieOpts.CookieExpire,
75+ CookieName: cookieOpts.CookieName,
76+ CookieSecret: cookieOpts.CookieSecret,
77+ }, nil
78+}
79+
80+// loadCookie retreieves the sessions state cookie from the http request.
81+// If a single cookie is present this will be returned, otherwise it attempts
82+// to reconstruct a cookie split up by splitCookie
83+func loadCookie(req *http.Request, cookieName string) (*http.Cookie, error) {
84+ c, err := req.Cookie(cookieName)
85+ if err == nil {
86+ return c, nil
87+ }
88+ cookies := []*http.Cookie{}
89+ err = nil
90+ count := 0
91+ for err == nil {
92+ var c *http.Cookie
93+ c, err = req.Cookie(fmt.Sprintf("%s_%d", cookieName, count))
94+ if err == nil {
95+ cookies = append(cookies, c)
96+ count++
97+ }
98+ }
99+ if len(cookies) == 0 {
100+ return nil, fmt.Errorf("Could not find cookie %s", cookieName)
101+ }
102+ return joinCookies(cookies)
103+}
104+
105+// joinCookies takes a slice of cookies from the request and reconstructs the
106+// full session cookie
107+func joinCookies(cookies []*http.Cookie) (*http.Cookie, error) {
108+ if len(cookies) == 0 {
109+ return nil, fmt.Errorf("list of cookies must be > 0")
110+ }
111+ if len(cookies) == 1 {
112+ return cookies[0], nil
113+ }
114+ c := copyCookie(cookies[0])
115+ for i := 1; i < len(cookies); i++ {
116+ c.Value += cookies[i].Value
117+ }
118+ c.Name = strings.TrimRight(c.Name, "_0")
119+ return c, nil
120+}
121+
122+func copyCookie(c *http.Cookie) *http.Cookie {
123+ return &http.Cookie{
124+ Name: c.Name,
125+ Value: c.Value,
126+ Path: c.Path,
127+ Domain: c.Domain,
128+ Expires: c.Expires,
129+ RawExpires: c.RawExpires,
130+ MaxAge: c.MaxAge,
131+ Secure: c.Secure,
132+ HttpOnly: c.HttpOnly,
133+ Raw: c.Raw,
134+ Unparsed: c.Unparsed,
135+ }
40136}
pkg/sessions/utils/utils.gonew file+41
@@ -0,0 +1,41 @@
1+package utils
2+
3+import (
4+ "encoding/base64"
5+
6+ "github.com/pusher/oauth2_proxy/cookie"
7+ "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
8+)
9+
10+// CookieForSession serializes a session state for storage in a cookie
11+func CookieForSession(s *sessions.SessionState, c *cookie.Cipher) (string, error) {
12+ return s.EncodeSessionState(c)
13+}
14+
15+// SessionFromCookie deserializes a session from a cookie value
16+func SessionFromCookie(v string, c *cookie.Cipher) (s *sessions.SessionState, err error) {
17+ return sessions.DecodeSessionState(v, c)
18+}
19+
20+// SecretBytes attempts to base64 decode the secret, if that fails it treats the secret as binary
21+func SecretBytes(secret string) []byte {
22+ b, err := base64.URLEncoding.DecodeString(addPadding(secret))
23+ if err == nil {
24+ return []byte(addPadding(string(b)))
25+ }
26+ return []byte(secret)
27+}
28+
29+func addPadding(secret string) string {
30+ padding := len(secret) % 4
31+ switch padding {
32+ case 1:
33+ return secret + "==="
34+ case 2:
35+ return secret + "=="
36+ case 3:
37+ return secret + "="
38+ default:
39+ return secret
40+ }
41+}