auth
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.
| 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 } |