auth

Paddy 2015-07-15 Parent:cf1aef6eb81f Child:b7e685839a1b

178:0a2c3d677161 Go to Latest

auth/authcode.go

Update to use a generic event emitter. Rather can creating a purpose-built event emitter for each and every event we need to emit (I'm looking at you, login verification event) which is _downright silly_, we're now using a generic event publisher that's based on saying "HEY A MODEL UPDATED". This means we need to change all our setup code in authd to use events.NewNSQPublisher or events.NewStdoutPublisher instead of our homegrown solutions. Which also means updating our config to take an events.Publisher instead of our LoginVerificationNotifier (blergh). Our Context also now uses an events.Publisher instead of a LoginVerificationNotifier. Party all around! We also replaced our SendLoginVerification helper method on Context with a SendModelEvent helper method on Context, which is just a light wrapper around events.PublishModelEvent. Of course, all this means we need to update our email_verification listener to listen to the correct channel (based on the model we want updates about) and filter down to a Created action or our new custom action for "the customer wants their verification resent", which I'm OK making a special case and not generic, because c'mon. But we had a subtle change to all our constants, some of which are unofficial constants now. I'm unsure how I feel about this. We also updated our email_verification listener so that we're unmarshalling to a custom loginEvent, which is just an events.Event that overwrites the Data property to be an auth.Login instance. This is to make sure we don't need to wrangle a map[string]interface{}, which is no fun. I'm also OK with special-casing like this, because it's 1) a tiny amount of code, 2) properly utilising composition, and 3) the only way I can think of to cleanly accomplish what I want. I also added a note about GetLogin's deficient handling of logins, namely that it doesn't recognise admins and return Verification codes to them, which would be a useful property for internal tools to take advantage of. Ah well. I updated the Profile and Login implementations so they're now event.Model instances, mainly by just exporting some strings from them through getters that will let us automatically build an Event from them. This lets us use the PublishModelEvent helper. I updated our CreateProfileHandler to properly mangle the login Verification property, and to fire off the ActionCreated events for the new Login and the new Profile. I updated our GetLoginHandler and UpdateLoginHandler to properly mangle the loginVerification property. God that's annoying. :-/ You'll note I didn't start publishing the events.ActionUpdated or events.ActionDeleted events for Profiles or Logins yet, and didn't bother publishing any events for literally any other type. That's because I'm a lazy piece of crap and will end up publishing them when I absolutely have to. Part of that is because if a channel isn't created/being read for a topic, the messages will just stack up in NSQ, and I don't want that. But mostly I'm lazy. Finally, I got to delete the entire profile_verification.go file, because we're no longer special-casing that. Hooray!

History
paddy@26 1 package auth
paddy@26 2
paddy@26 3 import (
paddy@84 4 "encoding/json"
paddy@29 5 "errors"
paddy@84 6 "net/http"
paddy@26 7 "time"
paddy@26 8
paddy@107 9 "code.secondbit.org/uuid.hg"
paddy@26 10 )
paddy@26 11
paddy@84 12 func init() {
paddy@84 13 RegisterGrantType("authorization_code", GrantType{
paddy@84 14 Validate: authCodeGrantValidate,
paddy@94 15 Invalidate: authCodeGrantInvalidate,
paddy@84 16 IssuesRefresh: true,
paddy@85 17 ReturnToken: RenderJSONToken,
paddy@123 18 AllowsPublic: true,
paddy@124 19 AuditString: authCodeGrantAuditString,
paddy@84 20 })
paddy@84 21 }
paddy@84 22
paddy@29 23 var (
paddy@87 24 // ErrNoAuthorizationCodeStore is returned when a Context tries to act on a authorizationCodeStore without setting one first.
paddy@87 25 ErrNoAuthorizationCodeStore = errors.New("no authorizationCodeStore was specified for the Context")
paddy@87 26 // ErrAuthorizationCodeNotFound is returned when an AuthorizationCode is requested but not found in the authorizationCodeStore.
paddy@87 27 ErrAuthorizationCodeNotFound = errors.New("authorization code not found in authorizationCodeStore")
paddy@87 28 // ErrAuthorizationCodeAlreadyExists is returned when an AuthorizationCode is added to a authorizationCodeStore, but another AuthorizationCode with the
paddy@87 29 // same Code already exists in the authorizationCodeStore.
paddy@87 30 ErrAuthorizationCodeAlreadyExists = errors.New("authorization code already exists in authorizationCodeStore")
paddy@29 31 )
paddy@29 32
paddy@87 33 // AuthorizationCode represents an authorization grant made by a user to a Client, to
paddy@57 34 // access user data within a defined Scope for a limited amount of time.
paddy@87 35 type AuthorizationCode struct {
paddy@26 36 Code string
paddy@26 37 Created time.Time
paddy@26 38 ExpiresIn int32
paddy@26 39 ClientID uuid.ID
paddy@163 40 Scopes Scopes
paddy@26 41 RedirectURI string
paddy@26 42 State string
paddy@69 43 ProfileID uuid.ID
paddy@94 44 Used bool
paddy@26 45 }
paddy@26 46
paddy@87 47 type authorizationCodeStore interface {
paddy@87 48 getAuthorizationCode(code string) (AuthorizationCode, error)
paddy@87 49 saveAuthorizationCode(authCode AuthorizationCode) error
paddy@87 50 deleteAuthorizationCode(code string) error
paddy@163 51 deleteAuthorizationCodesByProfileID(profileID uuid.ID) error
paddy@164 52 deleteAuthorizationCodesByClientID(clientID uuid.ID) error
paddy@94 53 useAuthorizationCode(code string) error
paddy@26 54 }
paddy@29 55
paddy@87 56 func (m *memstore) getAuthorizationCode(code string) (AuthorizationCode, error) {
paddy@87 57 m.authCodeLock.RLock()
paddy@87 58 defer m.authCodeLock.RUnlock()
paddy@87 59 authCode, ok := m.authCodes[code]
paddy@29 60 if !ok {
paddy@87 61 return AuthorizationCode{}, ErrAuthorizationCodeNotFound
paddy@29 62 }
paddy@87 63 return authCode, nil
paddy@29 64 }
paddy@29 65
paddy@87 66 func (m *memstore) saveAuthorizationCode(authCode AuthorizationCode) error {
paddy@87 67 m.authCodeLock.Lock()
paddy@87 68 defer m.authCodeLock.Unlock()
paddy@87 69 _, ok := m.authCodes[authCode.Code]
paddy@29 70 if ok {
paddy@87 71 return ErrAuthorizationCodeAlreadyExists
paddy@29 72 }
paddy@87 73 m.authCodes[authCode.Code] = authCode
paddy@29 74 return nil
paddy@29 75 }
paddy@29 76
paddy@87 77 func (m *memstore) deleteAuthorizationCode(code string) error {
paddy@87 78 m.authCodeLock.Lock()
paddy@87 79 defer m.authCodeLock.Unlock()
paddy@87 80 _, ok := m.authCodes[code]
paddy@29 81 if !ok {
paddy@87 82 return ErrAuthorizationCodeNotFound
paddy@29 83 }
paddy@87 84 delete(m.authCodes, code)
paddy@29 85 return nil
paddy@29 86 }
paddy@84 87
paddy@163 88 func (m *memstore) deleteAuthorizationCodesByProfileID(profileID uuid.ID) error {
paddy@163 89 m.authCodeLock.Lock()
paddy@163 90 defer m.authCodeLock.Unlock()
paddy@163 91 var codes []string
paddy@163 92 for _, code := range m.authCodes {
paddy@163 93 if code.ProfileID.Equal(profileID) {
paddy@163 94 codes = append(codes, code.Code)
paddy@163 95 }
paddy@163 96 }
paddy@163 97 if len(codes) < 1 {
paddy@163 98 return ErrProfileNotFound
paddy@163 99 }
paddy@163 100 for _, code := range codes {
paddy@163 101 delete(m.authCodes, code)
paddy@163 102 }
paddy@163 103 return nil
paddy@163 104 }
paddy@163 105
paddy@164 106 func (m *memstore) deleteAuthorizationCodesByClientID(clientID uuid.ID) error {
paddy@164 107 m.authCodeLock.Lock()
paddy@164 108 defer m.authCodeLock.Unlock()
paddy@164 109 var codes []string
paddy@164 110 for _, code := range m.authCodes {
paddy@164 111 if code.ClientID.Equal(clientID) {
paddy@164 112 codes = append(codes, code.Code)
paddy@164 113 }
paddy@164 114 }
paddy@164 115 if len(codes) < 1 {
paddy@164 116 return ErrClientNotFound
paddy@164 117 }
paddy@164 118 for _, code := range codes {
paddy@164 119 delete(m.authCodes, code)
paddy@164 120 }
paddy@164 121 return nil
paddy@164 122 }
paddy@164 123
paddy@94 124 func (m *memstore) useAuthorizationCode(code string) error {
paddy@94 125 m.authCodeLock.Lock()
paddy@94 126 defer m.authCodeLock.Unlock()
paddy@94 127 a, ok := m.authCodes[code]
paddy@94 128 if !ok {
paddy@94 129 return ErrAuthorizationCodeNotFound
paddy@94 130 }
paddy@94 131 a.Used = true
paddy@94 132 m.authCodes[code] = a
paddy@94 133 return nil
paddy@94 134 }
paddy@94 135
paddy@163 136 func authCodeGrantValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes Scopes, profileID uuid.ID, valid bool) {
paddy@84 137 enc := json.NewEncoder(w)
paddy@84 138 code := r.PostFormValue("code")
paddy@84 139 if code == "" {
paddy@84 140 w.WriteHeader(http.StatusBadRequest)
paddy@84 141 renderJSONError(enc, "invalid_request")
paddy@84 142 return
paddy@84 143 }
paddy@123 144 clientID, _, ok := getClientAuth(w, r, true)
paddy@123 145 if !ok {
paddy@84 146 return
paddy@84 147 }
paddy@87 148 authCode, err := context.GetAuthorizationCode(code)
paddy@84 149 if err != nil {
paddy@87 150 if err == ErrAuthorizationCodeNotFound {
paddy@84 151 w.WriteHeader(http.StatusBadRequest)
paddy@84 152 renderJSONError(enc, "invalid_grant")
paddy@84 153 return
paddy@84 154 }
paddy@84 155 w.WriteHeader(http.StatusInternalServerError)
paddy@84 156 renderJSONError(enc, "server_error")
paddy@84 157 return
paddy@84 158 }
paddy@85 159 redirectURI := r.PostFormValue("redirect_uri")
paddy@87 160 if authCode.RedirectURI != redirectURI {
paddy@84 161 w.WriteHeader(http.StatusBadRequest)
paddy@84 162 renderJSONError(enc, "invalid_grant")
paddy@84 163 return
paddy@84 164 }
paddy@87 165 if !authCode.ClientID.Equal(clientID) {
paddy@84 166 w.WriteHeader(http.StatusBadRequest)
paddy@84 167 renderJSONError(enc, "invalid_grant")
paddy@84 168 return
paddy@84 169 }
paddy@135 170 return authCode.Scopes, authCode.ProfileID, true
paddy@84 171 }
paddy@90 172
paddy@90 173 func authCodeGrantInvalidate(r *http.Request, context Context) error {
paddy@94 174 code := r.PostFormValue("code")
paddy@94 175 if code == "" {
paddy@94 176 return ErrAuthorizationCodeNotFound
paddy@94 177 }
paddy@94 178 return context.UseAuthorizationCode(code)
paddy@90 179 }
paddy@124 180
paddy@124 181 func authCodeGrantAuditString(r *http.Request) string {
paddy@124 182 return "authcode:" + r.PostFormValue("code")
paddy@124 183 }