auth

Paddy 2015-01-18 Parent:118a69954621 Child:0a1e16b9c141

122:eb9842ae3ff1 Go to Latest

auth/session.go

Enable the implict grant flow. Add the implicit grant flow. This can't be done in a grant type, because it's not specified through the grant_type parameter, for some absurd reason. Whatever. We basically achieved this by refactoring how we respond to the authorization endpoint, keying off the "response_type" parameter.

History
1 package auth
3 import (
4 "crypto/sha256"
5 "encoding/hex"
6 "encoding/json"
7 "errors"
8 "log"
9 "net/http"
10 "sort"
11 "time"
13 "code.secondbit.org/pass.hg"
14 "code.secondbit.org/uuid.hg"
15 "github.com/gorilla/mux"
16 )
18 const (
19 loginTemplateName = "login"
20 )
22 func init() {
23 RegisterGrantType("password", GrantType{
24 Validate: credentialsValidate,
25 Invalidate: nil,
26 IssuesRefresh: true,
27 ReturnToken: RenderJSONToken,
28 })
29 }
31 var (
32 // ErrNoSessionStore is returned when a Context tries to act on a sessionStore without setting on first.
33 ErrNoSessionStore = errors.New("no sessionStore was specified for the Context")
34 // ErrSessionNotFound is returned when a Session is requested but not found in the sessionStore.
35 ErrSessionNotFound = errors.New("session not found in sessionStore")
36 // ErrInvalidSession is returned when a Session is specified but is not valid.
37 ErrInvalidSession = errors.New("session is not valid")
38 // ErrSessionAlreadyExists is returned when a sessionStore tries to store a Session with an ID that already exists in the sessionStore.
39 ErrSessionAlreadyExists = errors.New("session already exists")
41 passphraseSchemes = map[int]passphraseScheme{
42 1: {
43 check: pbkdf2sha256check,
44 create: pbkdf2sha256create,
45 calculateIterations: pbkdf2sha256calc,
46 },
47 }
48 )
50 type passphraseScheme struct {
51 check func(profile Profile, passphrase string) (bool, error)
52 create func(passphrase string, iterations int) (result, salt string, err error)
53 calculateIterations func() (int, error)
54 }
56 // Session represents a user's authenticated session, associating it with a profile
57 // and some audit data.
58 type Session struct {
59 ID string
60 IP string
61 UserAgent string
62 ProfileID uuid.ID
63 Login string
64 Created time.Time
65 Active bool
66 }
68 type sortedSessions []Session
70 func (s sortedSessions) Len() int {
71 return len(s)
72 }
74 func (s sortedSessions) Less(i, j int) bool {
75 return s[i].Created.After(s[j].Created)
76 }
78 func (s sortedSessions) Swap(i, j int) {
79 s[i], s[j] = s[j], s[i]
80 }
82 type sessionStore interface {
83 createSession(session Session) error
84 getSession(id string) (Session, error)
85 removeSession(id string) error
86 listSessions(profile uuid.ID, before time.Time, num int64) ([]Session, error)
87 }
89 func (m *memstore) createSession(session Session) error {
90 m.sessionLock.Lock()
91 defer m.sessionLock.Unlock()
92 if _, ok := m.sessions[session.ID]; ok {
93 return ErrSessionAlreadyExists
94 }
95 m.sessions[session.ID] = session
96 return nil
97 }
99 func (m *memstore) getSession(id string) (Session, error) {
100 m.sessionLock.RLock()
101 defer m.sessionLock.RUnlock()
102 if _, ok := m.sessions[id]; !ok {
103 return Session{}, ErrSessionNotFound
104 }
105 return m.sessions[id], nil
106 }
108 func (m *memstore) removeSession(id string) error {
109 m.sessionLock.Lock()
110 defer m.sessionLock.Unlock()
111 if _, ok := m.sessions[id]; !ok {
112 return ErrSessionNotFound
113 }
114 delete(m.sessions, id)
115 return nil
116 }
118 func (m *memstore) listSessions(profile uuid.ID, before time.Time, num int64) ([]Session, error) {
119 m.sessionLock.RLock()
120 defer m.sessionLock.RUnlock()
121 res := []Session{}
122 for _, session := range m.sessions {
123 if int64(len(res)) >= num {
124 break
125 }
126 if profile != nil && !profile.Equal(session.ProfileID) {
127 continue
128 }
129 if !before.IsZero() && session.Created.After(before) {
130 continue
131 }
132 res = append(res, session)
133 }
134 sorted := sortedSessions(res)
135 sort.Sort(sorted)
136 res = []Session(sorted)
137 return res, nil
138 }
140 // RegisterSessionHandlers adds handlers to the passed router to handle the session endpoints, like login and logout.
141 func RegisterSessionHandlers(r *mux.Router, context Context) {
142 r.Handle("/login", wrap(context, CreateSessionHandler))
143 }
145 func checkCookie(r *http.Request, context Context) (Session, error) {
146 cookie, err := r.Cookie(authCookieName)
147 if err == http.ErrNoCookie {
148 return Session{}, ErrNoSession
149 } else if err != nil {
150 log.Println(err)
151 return Session{}, err
152 }
153 sess, err := context.GetSession(cookie.Value)
154 if err == ErrSessionNotFound {
155 return Session{}, ErrInvalidSession
156 } else if err != nil {
157 return Session{}, err
158 }
159 if !sess.Active {
160 return Session{}, ErrInvalidSession
161 }
162 return sess, nil
163 }
165 func buildLoginRedirect(r *http.Request, context Context) string {
166 if context.loginURI == nil {
167 return ""
168 }
169 uri := *context.loginURI
170 q := uri.Query()
171 q.Set("from", r.URL.String())
172 uri.RawQuery = q.Encode()
173 return uri.String()
174 }
176 func pbkdf2sha256check(profile Profile, passphrase string) (bool, error) {
177 realPass, err := hex.DecodeString(profile.Passphrase)
178 if err != nil {
179 return false, err
180 }
181 realSalt, err := hex.DecodeString(profile.Salt)
182 if err != nil {
183 return false, err
184 }
185 candidate := pass.Check(sha256.New, profile.Iterations, []byte(passphrase), []byte(realSalt))
186 if !pass.Compare(candidate, realPass) {
187 return false, ErrIncorrectAuth
188 }
189 return true, nil
190 }
192 func pbkdf2sha256create(passphrase string, iters int) (result, salt string, err error) {
193 passBytes, saltBytes, err := pass.Create(sha256.New, iters, []byte(passphrase))
194 if err != nil {
195 return "", "", err
196 }
197 result = hex.EncodeToString(passBytes)
198 salt = hex.EncodeToString(saltBytes)
199 return result, salt, err
200 }
202 func pbkdf2sha256calc() (int, error) {
203 return pass.CalculateIterations(sha256.New)
204 }
206 func authenticate(user, passphrase string, context Context) (Profile, error) {
207 profile, err := context.GetProfileByLogin(user)
208 if err != nil {
209 if err == ErrProfileNotFound || err == ErrLoginNotFound {
210 return Profile{}, ErrIncorrectAuth
211 }
212 return Profile{}, err
213 }
214 if profile.Compromised {
215 return Profile{}, ErrProfileCompromised
216 }
217 if !profile.LockedUntil.IsZero() && profile.LockedUntil.After(time.Now()) {
218 return profile, ErrProfileLocked
219 }
220 scheme, ok := passphraseSchemes[profile.PassphraseScheme]
221 if !ok {
222 return Profile{}, ErrInvalidPassphraseScheme
223 }
224 result, err := scheme.check(profile, passphrase)
225 if !result {
226 return Profile{}, err
227 }
228 return profile, nil
229 }
231 // CreateSessionHandler allows the user to log into their account and create their session.
232 func CreateSessionHandler(w http.ResponseWriter, r *http.Request, context Context) {
233 // BUG(paddy): Creating a session needs CSRF protection, right? This whole thing should get a security audit
234 errors := []error{}
235 if r.Method == "POST" {
236 profile, err := authenticate(r.PostFormValue("login"), r.PostFormValue("passphrase"), context)
237 if err == nil {
238 ip := r.Header.Get("X-Forwarded-For")
239 if ip == "" {
240 ip = r.RemoteAddr
241 }
242 session := Session{
243 ID: uuid.NewID().String(),
244 IP: ip,
245 UserAgent: r.UserAgent(),
246 ProfileID: profile.ID,
247 Login: r.PostFormValue("login"),
248 Created: time.Now(),
249 Active: true,
250 }
251 err = context.CreateSession(session)
252 if err != nil {
253 w.WriteHeader(http.StatusInternalServerError)
254 w.Write([]byte(err.Error()))
255 return
256 }
257 // BUG(paddy): really need to do a security audit on our cookie
258 cookie := http.Cookie{
259 Name: authCookieName,
260 Value: session.ID,
261 Expires: time.Now().Add(24 * 7 * time.Hour),
262 HttpOnly: true,
263 }
264 http.SetCookie(w, &cookie)
265 redirectTo := r.URL.Query().Get("from")
266 if redirectTo == "" {
267 redirectTo = "/"
268 }
269 http.Redirect(w, r, redirectTo, http.StatusFound)
270 return
271 } else if err != ErrIncorrectAuth && err != ErrProfileCompromised && err != ErrProfileLocked {
272 w.WriteHeader(http.StatusInternalServerError)
273 w.Write([]byte(err.Error()))
274 return
275 } else {
276 errors = append(errors, err)
277 }
278 }
279 context.Render(w, loginTemplateName, map[string]interface{}{
280 "errors": errors,
281 })
282 }
284 func credentialsValidate(w http.ResponseWriter, r *http.Request, context Context) (scope string, profileID uuid.ID, valid bool) {
285 enc := json.NewEncoder(w)
286 username := r.PostFormValue("username")
287 password := r.PostFormValue("password")
288 scope = r.PostFormValue("scope")
289 _, success := verifyClient(w, r, false, context)
290 if !success {
291 return
292 }
293 profile, err := authenticate(username, password, context)
294 if err != nil {
295 if err == ErrIncorrectAuth || err == ErrProfileCompromised || err == ErrProfileLocked {
296 w.WriteHeader(http.StatusBadRequest)
297 renderJSONError(enc, "invalid_grant")
298 return
299 }
300 w.WriteHeader(http.StatusInternalServerError)
301 w.Write([]byte(err.Error()))
302 return
303 }
304 profileID = profile.ID
305 valid = true
306 return
307 }