-
Notifications
You must be signed in to change notification settings - Fork 0
/
usersession.go
214 lines (164 loc) · 5.88 KB
/
usersession.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
package handlerware
import(
"fmt"
"log"
"net/http"
"net/http/httputil"
"time"
"golang.org/x/net/context"
gsessions "github.com/gorilla/sessions"
)
// FIXME: logging needs a real fix
func logPrintf(r *http.Request, fmtstr string, varargs ...interface{}) {
payload := fmt.Sprintf(fmtstr, varargs...)
prefix := fmt.Sprintf("ip:%s", r.Header.Get("x-appengine-user-ip"))
log.Printf("%s %s", prefix, payload)
}
var(
// CookieName is what the calling app wants its session token to be kept in.
CookieName = "choc_chip"
// NoSessionHandler is executed when the user doesn't have a session.
NoSessionHandler ContextHandler
sessionStore *gsessions.CookieStore
)
// InitSessionStore *must* be called in the caller's init() block.
func InitSessionStore(key, prevkey string) {
sessionStore = gsessions.NewCookieStore(
[]byte(key), nil,
[]byte(prevkey), nil)
sessionStore.MaxAge(86400 * 180)
}
// Pretty much all handlers should expect to be able to pluck this object out of their
// Context; see handlerware.go
type UserSession struct {
Email string // case sensitive, sadly
CreatedAt time.Time // when the user last went through the OAuth2 dance
}
func (us UserSession)IsEmpty() bool { return us.Email == "" }
func (us UserSession)IsAdmin() bool { return us.IsInGroup(AdminGroup) }
func (us UserSession)IsInGroup(g string) bool { return !us.IsEmpty() && IsInGroup(g,us.Email) }
// {{{ EnsureSession{OrFallback}
// EnsureSession checks that there is a user session, and if so runs the
// specified handler; else it runs the `NoSessionHandler` (which presumably
// starts a login flow). Adds some debug logging into a cookie, to try and
// illuminate how users end up without sessions.
func EnsureSession(ch ContextHandler) ContextHandler {
return EnsureSessionOrFallback(ch, NoSessionHandler)
}
// EnsureSessionOrFallback lets the caller specify which contexthandler
// to run when the session is not found.
func EnsureSessionOrFallback(ch,fallback ContextHandler) ContextHandler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
crumbs := CrumbTrail{}
crumbCookieName := CookieName + "crumbs"
// First, extract prev breadcrumbs and log them
cookies := map[string]string{}
for _,c := range r.Cookies() {
crumbs.Add("C:"+c.Name)
cookies[c.Name] = c.Value
}
if val,exists := cookies[crumbCookieName]; exists {
logPrintf(r, "%s in : %s", crumbCookieName, val)
}
handler := fallback
if _,exists := cookies[CookieName]; exists {
sesh,err := req2Session(r, &crumbs)
if err == nil && !sesh.IsEmpty() {
// Stash the session in the context, and move on to the proper handler
ctx = setUserSession(ctx, sesh)
handler = ch
} else {
if err != nil { logPrintf(r, "req2session err: " + err.Error()) }
logPrintf(r, "crumbs: " + crumbs.String())
}
} else {
crumbs.Add("NoMainCookie")
}
// Before invoking final handler, log breadcrumb trail, and stash in cookie
logPrintf(r, "%s out: %s", crumbCookieName, crumbs)
cookie := http.Cookie{
Name: crumbCookieName,
Value: crumbs.String(),
Expires:time.Now().AddDate(1,0,0),
}
http.SetCookie(w, &cookie)
reqLog,_ := httputil.DumpRequest(r,true)
logPrintf(r, "HTTP req>>>>\n%s====\n", reqLog)
if handler == nil {
logPrintf(r, "WithSession had no session, no NoSessionHandler")
http.Error(w, fmt.Sprintf("no session, no NoSessionHandler (%s)", r.URL), http.StatusInternalServerError)
return
}
handler(ctx, w, r)
}
}
// }}}
// {{{ {Get,set}UserSession
func GetUserSession(ctx context.Context) (UserSession, bool) {
opt, ok := ctx.Value(sessionKey).(UserSession)
return opt, ok
}
func setUserSession(ctx context.Context, sesh UserSession) context.Context {
return context.WithValue(ctx, sessionKey, sesh)
}
// }}}
// {{{ CreateSession
func CreateSession(ctx context.Context, w http.ResponseWriter, r *http.Request, sesh UserSession) {
session,err := sessionStore.Get(r, CookieName)
if err != nil {
// This isn't usually an important error (the session was most likely expired, which is why
// we're logging in) - so log as Info, not Error.
log.Printf("CreateSession: sessionStore.Get [failing is OK for this call] had err: %v", err)
}
session.Values["email"] = sesh.Email
session.Values["tstamp"] = time.Now().Format(time.RFC3339)
if err := session.Save(r,w); err != nil {
log.Printf("CreateSession: session.Save: %v", err)
}
log.Printf("CreateSession OK for %s", sesh.Email)
}
// }}}\
// {{{ OverwriteSessionToNil
func OverwriteSessionToNil(ctx context.Context, w http.ResponseWriter, r *http.Request) {
// Ignore errors; we just want an empty one
session,_ := sessionStore.Get(r, CookieName)
session.Values["email"] = nil
session.Values["tstamp"] = nil
session.Save(r, w)
log.Printf("OverwriteSessionToNil done")
}
// }}}
// {{{ req2Session
func req2Session(r *http.Request, crumbs *CrumbTrail) (UserSession, error) {
// If not found, returns an empty session
session,err := sessionStore.Get(r, CookieName)
if err != nil {
crumbs.Add("GDecodeFailed")
return UserSession{}, fmt.Errorf("Req2Session: sessionStore.Get: %v", err)
}
if session.IsNew {
crumbs.Add("NewGSession")
return UserSession{}, nil
} else if session.Values["email"] == nil {
crumbs.Add("LoggedOutSession")
return UserSession{}, nil
}
crumbs.Add("SessionRetrieved")
// crumbs.Add("E:"+session.Values["email"].(string))
tstampStr := session.Values["tstamp"].(string)
tstamp,_ := time.Parse(time.RFC3339, tstampStr)
crumbs.Add(fmt.Sprintf("Age:%s", time.Since(tstamp)))
userSesh := UserSession{
Email: session.Values["email"].(string),
CreatedAt: tstamp,
}
// In case of a new session object, give it a long cookie lifetime
//session.Options.MaxAge = 86400 * 180 // Default is 4w.
return userSesh, nil
}
// }}}
// {{{ -------------------------={ E N D }=----------------------------------
// Local variables:
// folded-file: t
// end:
// }}}