auth

Paddy 2015-04-19 Parent:2809016184f6 Child:cf1aef6eb81f

163:73e12d5a1124 Go to Latest

auth/authcode.go

Use postgres arrays for scope associations. Use the new pqarrays library I wrote to store Scope associations for Tokens and AuthorizationCodes, instead of using our hacky and abstraction-breaking many-to-many code. We also created the authStore.deleteAuthorizationCodesByProfileID method, to clear out the AuthorizationCodes that belong to a Profile (used when the Profile is deleted). So we added the implementation for memstore and for our postgres store. Call Context.DeleteAuthorizationCodesByProfileID when deleting a Profile to clean up after it. Rename sortedScopes to Scopes, which we use pqarrays.StringArray's methods on to fulfill the sql.Scanner and driver.Valuer interfaces. This lets us store Scopes in postgres arrays. Create a stringsToScopes helper function that creates Scope objects, with their IDs filled by the strings specified. Update our GrantType.Validate function signature to return Scopes instead of []string. Create a Scopes.Strings() helper method that returns a []string of the IDs of the Scopes. Update our SQL init file to use the new postgres array definition, instead of the many-to-many definition.

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@94 52 useAuthorizationCode(code string) error
paddy@26 53 }
paddy@29 54
paddy@87 55 func (m *memstore) getAuthorizationCode(code string) (AuthorizationCode, error) {
paddy@87 56 m.authCodeLock.RLock()
paddy@87 57 defer m.authCodeLock.RUnlock()
paddy@87 58 authCode, ok := m.authCodes[code]
paddy@29 59 if !ok {
paddy@87 60 return AuthorizationCode{}, ErrAuthorizationCodeNotFound
paddy@29 61 }
paddy@87 62 return authCode, nil
paddy@29 63 }
paddy@29 64
paddy@87 65 func (m *memstore) saveAuthorizationCode(authCode AuthorizationCode) error {
paddy@87 66 m.authCodeLock.Lock()
paddy@87 67 defer m.authCodeLock.Unlock()
paddy@87 68 _, ok := m.authCodes[authCode.Code]
paddy@29 69 if ok {
paddy@87 70 return ErrAuthorizationCodeAlreadyExists
paddy@29 71 }
paddy@87 72 m.authCodes[authCode.Code] = authCode
paddy@29 73 return nil
paddy@29 74 }
paddy@29 75
paddy@87 76 func (m *memstore) deleteAuthorizationCode(code string) error {
paddy@87 77 m.authCodeLock.Lock()
paddy@87 78 defer m.authCodeLock.Unlock()
paddy@87 79 _, ok := m.authCodes[code]
paddy@29 80 if !ok {
paddy@87 81 return ErrAuthorizationCodeNotFound
paddy@29 82 }
paddy@87 83 delete(m.authCodes, code)
paddy@29 84 return nil
paddy@29 85 }
paddy@84 86
paddy@163 87 func (m *memstore) deleteAuthorizationCodesByProfileID(profileID uuid.ID) error {
paddy@163 88 m.authCodeLock.Lock()
paddy@163 89 defer m.authCodeLock.Unlock()
paddy@163 90 var codes []string
paddy@163 91 for _, code := range m.authCodes {
paddy@163 92 if code.ProfileID.Equal(profileID) {
paddy@163 93 codes = append(codes, code.Code)
paddy@163 94 }
paddy@163 95 }
paddy@163 96 if len(codes) < 1 {
paddy@163 97 return ErrProfileNotFound
paddy@163 98 }
paddy@163 99 for _, code := range codes {
paddy@163 100 delete(m.authCodes, code)
paddy@163 101 }
paddy@163 102 return nil
paddy@163 103 }
paddy@163 104
paddy@94 105 func (m *memstore) useAuthorizationCode(code string) error {
paddy@94 106 m.authCodeLock.Lock()
paddy@94 107 defer m.authCodeLock.Unlock()
paddy@94 108 a, ok := m.authCodes[code]
paddy@94 109 if !ok {
paddy@94 110 return ErrAuthorizationCodeNotFound
paddy@94 111 }
paddy@94 112 a.Used = true
paddy@94 113 m.authCodes[code] = a
paddy@94 114 return nil
paddy@94 115 }
paddy@94 116
paddy@163 117 func authCodeGrantValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes Scopes, profileID uuid.ID, valid bool) {
paddy@84 118 enc := json.NewEncoder(w)
paddy@84 119 code := r.PostFormValue("code")
paddy@84 120 if code == "" {
paddy@84 121 w.WriteHeader(http.StatusBadRequest)
paddy@84 122 renderJSONError(enc, "invalid_request")
paddy@84 123 return
paddy@84 124 }
paddy@123 125 clientID, _, ok := getClientAuth(w, r, true)
paddy@123 126 if !ok {
paddy@84 127 return
paddy@84 128 }
paddy@87 129 authCode, err := context.GetAuthorizationCode(code)
paddy@84 130 if err != nil {
paddy@87 131 if err == ErrAuthorizationCodeNotFound {
paddy@84 132 w.WriteHeader(http.StatusBadRequest)
paddy@84 133 renderJSONError(enc, "invalid_grant")
paddy@84 134 return
paddy@84 135 }
paddy@84 136 w.WriteHeader(http.StatusInternalServerError)
paddy@84 137 renderJSONError(enc, "server_error")
paddy@84 138 return
paddy@84 139 }
paddy@85 140 redirectURI := r.PostFormValue("redirect_uri")
paddy@87 141 if authCode.RedirectURI != redirectURI {
paddy@84 142 w.WriteHeader(http.StatusBadRequest)
paddy@84 143 renderJSONError(enc, "invalid_grant")
paddy@84 144 return
paddy@84 145 }
paddy@87 146 if !authCode.ClientID.Equal(clientID) {
paddy@84 147 w.WriteHeader(http.StatusBadRequest)
paddy@84 148 renderJSONError(enc, "invalid_grant")
paddy@84 149 return
paddy@84 150 }
paddy@135 151 return authCode.Scopes, authCode.ProfileID, true
paddy@84 152 }
paddy@90 153
paddy@90 154 func authCodeGrantInvalidate(r *http.Request, context Context) error {
paddy@94 155 code := r.PostFormValue("code")
paddy@94 156 if code == "" {
paddy@94 157 return ErrAuthorizationCodeNotFound
paddy@94 158 }
paddy@94 159 return context.UseAuthorizationCode(code)
paddy@90 160 }
paddy@124 161
paddy@124 162 func authCodeGrantAuditString(r *http.Request) string {
paddy@124 163 return "authcode:" + r.PostFormValue("code")
paddy@124 164 }