git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add CreatedAt to SessionState
commit34cbe0497cace51da031e8e8ba30c18223cdfab5 authorJoel Speed <Joel.speed@hotmail.co.uk> dateTue 7 May 2019, 15:32 (7 years ago) parentfbee5ea
7 files changed+57−8
pkg/apis/sessions/session_state.go+20−1 pkg/apis/sessions/session_state_test.go+27−2 pkg/sessions/cookie/session_store.go+4−4 providers/google.go+1 providers/logingov.go+1 providers/oidc.go+2 providers/provider_default.go+2−1
pkg/apis/sessions/session_state.go+20−1
@@ -14,6 +14,7 @@ import (
1414type SessionState struct {
1515 AccessToken string `json:",omitempty"`
1616 IDToken string `json:",omitempty"`
17+ CreatedAt time.Time `json:"-"`
1718 ExpiresOn time.Time `json:"-"`
1819 RefreshToken string `json:",omitempty"`
1920 Email string `json:",omitempty"`
@@ -23,6 +24,7 @@ type SessionState struct {
2324// SessionStateJSON is used to encode SessionState into JSON without exposing time.Time zero value
2425type SessionStateJSON struct {
2526 *SessionState
27+ CreatedAt *time.Time `json:",omitempty"`
2628 ExpiresOn *time.Time `json:",omitempty"`
2729}
2830
@@ -34,6 +36,14 @@ func (s *SessionState) IsExpired() bool {
3436 return false
3537}
3638
39+// Age returns the age of a session
40+func (s *SessionState) Age() time.Duration {
41+ if !s.CreatedAt.IsZero() {
42+ return time.Now().Truncate(time.Second).Sub(s.CreatedAt)
43+ }
44+ return 0
45+}
46+
3747// String constructs a summary of the session state
3848func (s *SessionState) String() string {
3949 o := fmt.Sprintf("Session{email:%s user:%s", s.Email, s.User)
@@ -43,6 +53,9 @@ func (s *SessionState) String() string {
4353 if s.IDToken != "" {
4454 o += " id_token:true"
4555 }
56+ if !s.CreatedAt.IsZero() {
57+ o += fmt.Sprintf(" created:%s", s.CreatedAt)
58+ }
4659 if !s.ExpiresOn.IsZero() {
4760 o += fmt.Sprintf(" expires:%s", s.ExpiresOn)
4861 }
@@ -95,6 +108,9 @@ func (s *SessionState) EncodeSessionState(c *cookie.Cipher) (string, error) {
95108 }
96109 // Embed SessionState and ExpiresOn pointer into SessionStateJSON
97110 ssj := &SessionStateJSON{SessionState: &ss}
111+ if !ss.CreatedAt.IsZero() {
112+ ssj.CreatedAt = &ss.CreatedAt
113+ }
98114 if !ss.ExpiresOn.IsZero() {
99115 ssj.ExpiresOn = &ss.ExpiresOn
100116 }
@@ -165,8 +181,11 @@ func DecodeSessionState(v string, c *cookie.Cipher) (*SessionState, error) {
165181 var ss *SessionState
166182 err := json.Unmarshal([]byte(v), &ssj)
167183 if err == nil && ssj.SessionState != nil {
168− // Extract SessionState and ExpiresOn value from SessionStateJSON
184+ // Extract SessionState and CreatedAt,ExpiresOn value from SessionStateJSON
169185 ss = ssj.SessionState
186+ if ssj.CreatedAt != nil {
187+ ss.CreatedAt = *ssj.CreatedAt
188+ }
170189 if ssj.ExpiresOn != nil {
171190 ss.ExpiresOn = *ssj.ExpiresOn
172191 }
pkg/apis/sessions/session_state_test.go+27−2
@@ -22,6 +22,7 @@ func TestSessionStateSerialization(t *testing.T) {
2222 Email: "user@domain.com",
2323 AccessToken: "token1234",
2424 IDToken: "rawtoken1234",
25+ CreatedAt: time.Now(),
2526 ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
2627 RefreshToken: "refresh4321",
2728 }
@@ -35,6 +36,7 @@ func TestSessionStateSerialization(t *testing.T) {
3536 assert.Equal(t, s.Email, ss.Email)
3637 assert.Equal(t, s.AccessToken, ss.AccessToken)
3738 assert.Equal(t, s.IDToken, ss.IDToken)
39+ assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix())
3840 assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
3941 assert.Equal(t, s.RefreshToken, ss.RefreshToken)
4042
@@ -44,6 +46,7 @@ func TestSessionStateSerialization(t *testing.T) {
4446 assert.Equal(t, nil, err)
4547 assert.NotEqual(t, "user@domain.com", ss.User)
4648 assert.NotEqual(t, s.Email, ss.Email)
49+ assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix())
4750 assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
4851 assert.NotEqual(t, s.AccessToken, ss.AccessToken)
4952 assert.NotEqual(t, s.IDToken, ss.IDToken)
@@ -59,6 +62,7 @@ func TestSessionStateSerializationWithUser(t *testing.T) {
5962 User: "just-user",
6063 Email: "user@domain.com",
6164 AccessToken: "token1234",
65+ CreatedAt: time.Now(),
6266 ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
6367 RefreshToken: "refresh4321",
6468 }
@@ -71,6 +75,7 @@ func TestSessionStateSerializationWithUser(t *testing.T) {
7175 assert.Equal(t, s.User, ss.User)
7276 assert.Equal(t, s.Email, ss.Email)
7377 assert.Equal(t, s.AccessToken, ss.AccessToken)
78+ assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix())
7479 assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
7580 assert.Equal(t, s.RefreshToken, ss.RefreshToken)
7681
@@ -80,6 +85,7 @@ func TestSessionStateSerializationWithUser(t *testing.T) {
8085 assert.Equal(t, nil, err)
8186 assert.NotEqual(t, s.User, ss.User)
8287 assert.NotEqual(t, s.Email, ss.Email)
88+ assert.Equal(t, s.CreatedAt.Unix(), ss.CreatedAt.Unix())
8389 assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
8490 assert.NotEqual(t, s.AccessToken, ss.AccessToken)
8591 assert.NotEqual(t, s.RefreshToken, ss.RefreshToken)
@@ -89,6 +95,7 @@ func TestSessionStateSerializationNoCipher(t *testing.T) {
8995 s := &sessions.SessionState{
9096 Email: "user@domain.com",
9197 AccessToken: "token1234",
98+ CreatedAt: time.Now(),
9299 ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
93100 RefreshToken: "refresh4321",
94101 }
@@ -109,6 +116,7 @@ func TestSessionStateSerializationNoCipherWithUser(t *testing.T) {
109116 User: "just-user",
110117 Email: "user@domain.com",
111118 AccessToken: "token1234",
119+ CreatedAt: time.Now(),
112120 ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
113121 RefreshToken: "refresh4321",
114122 }
@@ -147,6 +155,7 @@ type testCase struct {
147155// Currently only tests without cipher here because we have no way to mock
148156// the random generator used in EncodeSessionState.
149157func TestEncodeSessionState(t *testing.T) {
158+ c := time.Now()
150159 e := time.Now().Add(time.Duration(1) * time.Hour)
151160
152161 testCases := []testCase{
@@ -163,6 +172,7 @@ func TestEncodeSessionState(t *testing.T) {
163172 User: "just-user",
164173 AccessToken: "token1234",
165174 IDToken: "rawtoken1234",
175+ CreatedAt: c,
166176 ExpiresOn: e,
167177 RefreshToken: "refresh4321",
168178 },
@@ -185,6 +195,9 @@ func TestEncodeSessionState(t *testing.T) {
185195
186196// TestDecodeSessionState testssessions.DecodeSessionState with the test vector
187197func TestDecodeSessionState(t *testing.T) {
198+ created := time.Now()
199+ createdJSON, _ := created.MarshalJSON()
200+ createdString := string(createdJSON)
188201 e := time.Now().Add(time.Duration(1) * time.Hour)
189202 eJSON, _ := e.MarshalJSON()
190203 eString := string(eJSON)
@@ -219,7 +232,7 @@ func TestDecodeSessionState(t *testing.T) {
219232 Email: "user@domain.com",
220233 User: "just-user",
221234 },
222− Encoded: fmt.Sprintf(`{"Email":"user@domain.com","User":"just-user","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","ExpiresOn":%s}`, eString),
235+ Encoded: fmt.Sprintf(`{"Email":"user@domain.com","User":"just-user","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","CreatedAt":%s,"ExpiresOn":%s}`, createdString, eString),
223236 },
224237 {
225238 SessionState: sessions.SessionState{
@@ -227,10 +240,11 @@ func TestDecodeSessionState(t *testing.T) {
227240 User: "just-user",
228241 AccessToken: "token1234",
229242 IDToken: "rawtoken1234",
243+ CreatedAt: created,
230244 ExpiresOn: e,
231245 RefreshToken: "refresh4321",
232246 },
233− Encoded: fmt.Sprintf(`{"Email":"FsKKYrTWZWrxSOAqA/fTNAUZS5QWCqOBjuAbBlbVOw==","User":"rT6JP3dxQhxUhkWrrd7yt6c1mDVyQCVVxw==","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","ExpiresOn":%s}`, eString),
247+ Encoded: fmt.Sprintf(`{"Email":"FsKKYrTWZWrxSOAqA/fTNAUZS5QWCqOBjuAbBlbVOw==","User":"rT6JP3dxQhxUhkWrrd7yt6c1mDVyQCVVxw==","AccessToken":"I6s+ml+/MldBMgHIiC35BTKTh57skGX24w==","IDToken":"xojNdyyjB1HgYWh6XMtXY/Ph5eCVxa1cNsklJw==","RefreshToken":"qEX0x6RmASxo4dhlBG6YuRs9Syn/e9sHu/+K","CreatedAt":%s,"ExpiresOn":%s}`, createdString, eString),
234248 Cipher: c,
235249 },
236250 {
@@ -316,3 +330,14 @@ func TestDecodeSessionState(t *testing.T) {
316330 }
317331 }
318332}
333+
334+func TestSessionStateAge(t *testing.T) {
335+ ss := &sessions.SessionState{}
336+
337+ // Created at unset so should be 0
338+ assert.Equal(t, time.Duration(0), ss.Age())
339+
340+ // Set CreatedAt to 1 hour ago
341+ ss.CreatedAt = time.Now().Add(-1 * time.Hour)
342+ assert.Equal(t, time.Hour, ss.Age().Round(time.Minute))
343+}
pkg/sessions/cookie/session_store.go+4−4
@@ -78,7 +78,7 @@ func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
7878
7979 for _, c := range req.Cookies() {
8080 if cookieNameRegex.MatchString(c.Name) {
81− clearCookie := s.makeCookie(req, c.Name, "", time.Hour*-1)
81+ clearCookie := s.makeCookie(req, c.Name, "", time.Hour*-1, time.Now())
8282
8383 http.SetCookie(rw, clearCookie)
8484 cookies = append(cookies, clearCookie)
@@ -101,14 +101,14 @@ func (s *SessionStore) makeSessionCookie(req *http.Request, value string, expira
101101 if value != "" {
102102 value = cookie.SignedValue(s.CookieSecret, s.CookieName, value, now)
103103 }
104− c := s.makeCookie(req, s.CookieName, value, expiration)
104+ c := s.makeCookie(req, s.CookieName, value, expiration, now)
105105 if len(c.Value) > 4096-len(s.CookieName) {
106106 return splitCookie(c)
107107 }
108108 return []*http.Cookie{c}
109109}
110110
111−func (s *SessionStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration) *http.Cookie {
111+func (s *SessionStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie {
112112 return cookies.MakeCookie(
113113 req,
114114 name,
@@ -118,7 +118,7 @@ func (s *SessionStore) makeCookie(req *http.Request, name string, value string,
118118 s.CookieHTTPOnly,
119119 s.CookieSecure,
120120 expiration,
121− time.Now(),
121+ now,
122122 )
123123}
124124
providers/google.go+1
@@ -149,6 +149,7 @@ func (p *GoogleProvider) Redeem(redirectURL, code string) (s *sessions.SessionSt
149149 s = &sessions.SessionState{
150150 AccessToken: jsonResponse.AccessToken,
151151 IDToken: jsonResponse.IDToken,
152+ CreatedAt: time.Now(),
152153 ExpiresOn: time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second),
153154 RefreshToken: jsonResponse.RefreshToken,
154155 Email: c.Email,
providers/logingov.go+1
@@ -252,6 +252,7 @@ func (p *LoginGovProvider) Redeem(redirectURL, code string) (s *sessions.Session
252252 s = &sessions.SessionState{
253253 AccessToken: jsonResponse.AccessToken,
254254 IDToken: jsonResponse.IDToken,
255+ CreatedAt: time.Now(),
255256 ExpiresOn: time.Now().Add(time.Duration(jsonResponse.ExpiresIn) * time.Second).Truncate(time.Second),
256257 Email: email,
257258 }
providers/oidc.go+2
@@ -87,6 +87,7 @@ func (p *OIDCProvider) redeemRefreshToken(s *sessions.SessionState) (err error)
8787 s.AccessToken = newSession.AccessToken
8888 s.IDToken = newSession.IDToken
8989 s.RefreshToken = newSession.RefreshToken
90+ s.CreatedAt = newSession.CreatedAt
9091 s.ExpiresOn = newSession.ExpiresOn
9192 s.Email = newSession.Email
9293 return
@@ -126,6 +127,7 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok
126127 AccessToken: token.AccessToken,
127128 IDToken: rawIDToken,
128129 RefreshToken: token.RefreshToken,
130+ CreatedAt: time.Now(),
129131 ExpiresOn: token.Expiry,
130132 Email: claims.Email,
131133 User: claims.Subject,
providers/provider_default.go+2−1
@@ -8,6 +8,7 @@ import (
88 "io/ioutil"
99 "net/http"
1010 "net/url"
11+ "time"
1112
1213 "github.com/pusher/oauth2_proxy/cookie"
1314 "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
@@ -72,7 +73,7 @@ func (p *ProviderData) Redeem(redirectURL, code string) (s *sessions.SessionStat
7273 return
7374 }
7475 if a := v.Get("access_token"); a != "" {
75− s = &sessions.SessionState{AccessToken: a}
76+ s = &sessions.SessionState{AccessToken: a, CreatedAt: time.Now()}
7677 } else {
7778 err = fmt.Errorf("no access token found %s", body)
7879 }