auth

Paddy 2014-12-06 Parent:42bc3e44f4fe Child:1dc4e152e3b0

84:4cb65cf90217 Go to Latest

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.

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@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 }