auth
auth/grant.go
Start supporting our pluggable grant_type. Define GrantType as a way to bundle information that can be used to validate requests based on their grant_type parameter. Move our validation of the authorization_code grant_type out of GetTokenHandler and into its own function. Define RegisterGrantType as a way to register new grant_type bundles and associate them with the string passed to grant_type. This enables other packages to define RegisterGrantType in their init() functions and plug in new grant types without forking this code. Implement RegisterGrantType for our authorization_code grant type.
| 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@45 | 9 "code.secondbit.org/uuid" |
| paddy@26 | 10 ) |
| paddy@26 | 11 |
| paddy@84 | 12 func init() { |
| paddy@84 | 13 RegisterGrantType("authorization_code", GrantType{ |
| paddy@84 | 14 Validate: authCodeGrantValidate, |
| paddy@84 | 15 IssuesRefresh: true, |
| paddy@84 | 16 }) |
| paddy@84 | 17 } |
| paddy@84 | 18 |
| paddy@29 | 19 var ( |
| paddy@57 | 20 // ErrNoGrantStore is returned when a Context tries to act on a grantStore without setting one first. |
| paddy@57 | 21 ErrNoGrantStore = errors.New("no grantStore was specified for the Context") |
| paddy@57 | 22 // ErrGrantNotFound is returned when a Grant is requested but not found in the grantStore. |
| paddy@57 | 23 ErrGrantNotFound = errors.New("grant not found in grantStore") |
| paddy@57 | 24 // ErrGrantAlreadyExists is returned when a Grant is added to a grantStore, but another Grant with the |
| paddy@57 | 25 // same Code already exists in the grantStore. |
| paddy@57 | 26 ErrGrantAlreadyExists = errors.New("grant already exists in grantStore") |
| paddy@29 | 27 ) |
| paddy@29 | 28 |
| paddy@57 | 29 // Grant represents an authorization grant made by a user to a Client, to |
| paddy@57 | 30 // access user data within a defined Scope for a limited amount of time. |
| paddy@26 | 31 type Grant struct { |
| paddy@26 | 32 Code string |
| paddy@26 | 33 Created time.Time |
| paddy@26 | 34 ExpiresIn int32 |
| paddy@26 | 35 ClientID uuid.ID |
| paddy@26 | 36 Scope string |
| paddy@26 | 37 RedirectURI string |
| paddy@26 | 38 State string |
| paddy@69 | 39 ProfileID uuid.ID |
| paddy@26 | 40 } |
| paddy@26 | 41 |
| paddy@57 | 42 type grantStore interface { |
| paddy@57 | 43 getGrant(code string) (Grant, error) |
| paddy@57 | 44 saveGrant(grant Grant) error |
| paddy@57 | 45 deleteGrant(code string) error |
| paddy@26 | 46 } |
| paddy@29 | 47 |
| paddy@57 | 48 func (m *memstore) getGrant(code string) (Grant, error) { |
| paddy@29 | 49 m.grantLock.RLock() |
| paddy@29 | 50 defer m.grantLock.RUnlock() |
| paddy@29 | 51 grant, ok := m.grants[code] |
| paddy@29 | 52 if !ok { |
| paddy@29 | 53 return Grant{}, ErrGrantNotFound |
| paddy@29 | 54 } |
| paddy@29 | 55 return grant, nil |
| paddy@29 | 56 } |
| paddy@29 | 57 |
| paddy@57 | 58 func (m *memstore) saveGrant(grant Grant) error { |
| paddy@29 | 59 m.grantLock.Lock() |
| paddy@29 | 60 defer m.grantLock.Unlock() |
| paddy@29 | 61 _, ok := m.grants[grant.Code] |
| paddy@29 | 62 if ok { |
| paddy@29 | 63 return ErrGrantAlreadyExists |
| paddy@29 | 64 } |
| paddy@29 | 65 m.grants[grant.Code] = grant |
| paddy@29 | 66 return nil |
| paddy@29 | 67 } |
| paddy@29 | 68 |
| paddy@57 | 69 func (m *memstore) deleteGrant(code string) error { |
| paddy@29 | 70 m.grantLock.Lock() |
| paddy@29 | 71 defer m.grantLock.Unlock() |
| paddy@29 | 72 _, ok := m.grants[code] |
| paddy@29 | 73 if !ok { |
| paddy@29 | 74 return ErrGrantNotFound |
| paddy@29 | 75 } |
| paddy@29 | 76 delete(m.grants, code) |
| paddy@29 | 77 return nil |
| paddy@29 | 78 } |
| paddy@84 | 79 |
| paddy@84 | 80 func authCodeGrantValidate(w http.ResponseWriter, r *http.Request, context Context) (scope string, profileID uuid.ID, valid bool) { |
| paddy@84 | 81 enc := json.NewEncoder(w) |
| paddy@84 | 82 code := r.PostFormValue("code") |
| paddy@84 | 83 if code == "" { |
| paddy@84 | 84 w.WriteHeader(http.StatusBadRequest) |
| paddy@84 | 85 renderJSONError(enc, "invalid_request") |
| paddy@84 | 86 return |
| paddy@84 | 87 } |
| paddy@84 | 88 // BUG(paddy): We really ought to break client verification out into its own helper functions, but I think it may depend on which grant_type is used... |
| paddy@84 | 89 redirectURI := r.PostFormValue("redirect_uri") |
| paddy@84 | 90 clientIDStr, clientSecret, fromAuthHeader := r.BasicAuth() |
| paddy@84 | 91 if !fromAuthHeader { |
| paddy@84 | 92 clientIDStr = r.PostFormValue("client_id") |
| paddy@84 | 93 } |
| paddy@84 | 94 clientID, err := uuid.Parse(clientIDStr) |
| paddy@84 | 95 if err != nil { |
| paddy@84 | 96 w.WriteHeader(http.StatusUnauthorized) |
| paddy@84 | 97 if fromAuthHeader { |
| paddy@84 | 98 w.Header().Set("WWW-Authenticate", "Basic") |
| paddy@84 | 99 } |
| paddy@84 | 100 renderJSONError(enc, "invalid_client") |
| paddy@84 | 101 return |
| paddy@84 | 102 } |
| paddy@84 | 103 client, err := context.GetClient(clientID) |
| paddy@84 | 104 if err != nil { |
| paddy@84 | 105 if err == ErrClientNotFound { |
| paddy@84 | 106 w.WriteHeader(http.StatusUnauthorized) |
| paddy@84 | 107 renderJSONError(enc, "invalid_client") |
| paddy@84 | 108 } else { |
| paddy@84 | 109 w.WriteHeader(http.StatusInternalServerError) |
| paddy@84 | 110 renderJSONError(enc, "server_error") |
| paddy@84 | 111 } |
| paddy@84 | 112 return |
| paddy@84 | 113 } |
| paddy@84 | 114 if client.Secret != clientSecret { |
| paddy@84 | 115 w.WriteHeader(http.StatusUnauthorized) |
| paddy@84 | 116 if fromAuthHeader { |
| paddy@84 | 117 w.Header().Set("WWW-Authenticate", "Basic") |
| paddy@84 | 118 } |
| paddy@84 | 119 renderJSONError(enc, "invalid_client") |
| paddy@84 | 120 return |
| paddy@84 | 121 } |
| paddy@84 | 122 grant, err := context.GetGrant(code) |
| paddy@84 | 123 if err != nil { |
| paddy@84 | 124 if err == ErrGrantNotFound { |
| paddy@84 | 125 w.WriteHeader(http.StatusBadRequest) |
| paddy@84 | 126 renderJSONError(enc, "invalid_grant") |
| paddy@84 | 127 return |
| paddy@84 | 128 } |
| paddy@84 | 129 w.WriteHeader(http.StatusInternalServerError) |
| paddy@84 | 130 renderJSONError(enc, "server_error") |
| paddy@84 | 131 return |
| paddy@84 | 132 } |
| paddy@84 | 133 if grant.RedirectURI != redirectURI { |
| paddy@84 | 134 w.WriteHeader(http.StatusBadRequest) |
| paddy@84 | 135 renderJSONError(enc, "invalid_grant") |
| paddy@84 | 136 return |
| paddy@84 | 137 } |
| paddy@84 | 138 if !grant.ClientID.Equal(clientID) { |
| paddy@84 | 139 w.WriteHeader(http.StatusBadRequest) |
| paddy@84 | 140 renderJSONError(enc, "invalid_grant") |
| paddy@84 | 141 return |
| paddy@84 | 142 } |
| paddy@84 | 143 return grant.Scope, grant.ProfileID, true |
| paddy@84 | 144 } |