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.
1 package auth
3 import (
4 "encoding/json"
5 "errors"
6 "net/http"
7 "time"
9 "code.secondbit.org/uuid"
10 )
12 func init() {
13 RegisterGrantType("authorization_code", GrantType{
14 Validate: authCodeGrantValidate,
15 IssuesRefresh: true,
16 })
17 }
19 var (
20 // ErrNoGrantStore is returned when a Context tries to act on a grantStore without setting one first.
21 ErrNoGrantStore = errors.New("no grantStore was specified for the Context")
22 // ErrGrantNotFound is returned when a Grant is requested but not found in the grantStore.
23 ErrGrantNotFound = errors.New("grant not found in grantStore")
24 // ErrGrantAlreadyExists is returned when a Grant is added to a grantStore, but another Grant with the
25 // same Code already exists in the grantStore.
26 ErrGrantAlreadyExists = errors.New("grant already exists in grantStore")
27 )
29 // Grant represents an authorization grant made by a user to a Client, to
30 // access user data within a defined Scope for a limited amount of time.
31 type Grant struct {
32 Code string
33 Created time.Time
34 ExpiresIn int32
35 ClientID uuid.ID
36 Scope string
37 RedirectURI string
38 State string
39 ProfileID uuid.ID
40 }
42 type grantStore interface {
43 getGrant(code string) (Grant, error)
44 saveGrant(grant Grant) error
45 deleteGrant(code string) error
46 }
48 func (m *memstore) getGrant(code string) (Grant, error) {
49 m.grantLock.RLock()
50 defer m.grantLock.RUnlock()
51 grant, ok := m.grants[code]
52 if !ok {
53 return Grant{}, ErrGrantNotFound
54 }
55 return grant, nil
56 }
58 func (m *memstore) saveGrant(grant Grant) error {
59 m.grantLock.Lock()
60 defer m.grantLock.Unlock()
61 _, ok := m.grants[grant.Code]
62 if ok {
63 return ErrGrantAlreadyExists
64 }
65 m.grants[grant.Code] = grant
66 return nil
67 }
69 func (m *memstore) deleteGrant(code string) error {
70 m.grantLock.Lock()
71 defer m.grantLock.Unlock()
72 _, ok := m.grants[code]
73 if !ok {
74 return ErrGrantNotFound
75 }
76 delete(m.grants, code)
77 return nil
78 }
80 func authCodeGrantValidate(w http.ResponseWriter, r *http.Request, context Context) (scope string, profileID uuid.ID, valid bool) {
81 enc := json.NewEncoder(w)
82 code := r.PostFormValue("code")
83 if code == "" {
84 w.WriteHeader(http.StatusBadRequest)
85 renderJSONError(enc, "invalid_request")
86 return
87 }
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...
89 redirectURI := r.PostFormValue("redirect_uri")
90 clientIDStr, clientSecret, fromAuthHeader := r.BasicAuth()
91 if !fromAuthHeader {
92 clientIDStr = r.PostFormValue("client_id")
93 }
94 clientID, err := uuid.Parse(clientIDStr)
95 if err != nil {
96 w.WriteHeader(http.StatusUnauthorized)
97 if fromAuthHeader {
98 w.Header().Set("WWW-Authenticate", "Basic")
99 }
100 renderJSONError(enc, "invalid_client")
101 return
102 }
103 client, err := context.GetClient(clientID)
104 if err != nil {
105 if err == ErrClientNotFound {
106 w.WriteHeader(http.StatusUnauthorized)
107 renderJSONError(enc, "invalid_client")
108 } else {
109 w.WriteHeader(http.StatusInternalServerError)
110 renderJSONError(enc, "server_error")
111 }
112 return
113 }
114 if client.Secret != clientSecret {
115 w.WriteHeader(http.StatusUnauthorized)
116 if fromAuthHeader {
117 w.Header().Set("WWW-Authenticate", "Basic")
118 }
119 renderJSONError(enc, "invalid_client")
120 return
121 }
122 grant, err := context.GetGrant(code)
123 if err != nil {
124 if err == ErrGrantNotFound {
125 w.WriteHeader(http.StatusBadRequest)
126 renderJSONError(enc, "invalid_grant")
127 return
128 }
129 w.WriteHeader(http.StatusInternalServerError)
130 renderJSONError(enc, "server_error")
131 return
132 }
133 if grant.RedirectURI != redirectURI {
134 w.WriteHeader(http.StatusBadRequest)
135 renderJSONError(enc, "invalid_grant")
136 return
137 }
138 if !grant.ClientID.Equal(clientID) {
139 w.WriteHeader(http.StatusBadRequest)
140 renderJSONError(enc, "invalid_grant")
141 return
142 }
143 return grant.Scope, grant.ProfileID, true
144 }