Bugfixes and tests for getting grants.
Add tests for the grant confirmation part of the request to get a grant code.
When the request is denied, the redirect should have an access_denied error.
When the request is approved, the redirect should contain a code.
Fix numerous bugs in which the redirect URL didn't contain the parameters we
thought it would. Basically, anything that still used req.URL.Query().Set()
instead of copying the query, modifying it, and setting req.URL.RawQuery.
Fix a bug in which the redirectURL wasn't being properly set. Basically, when we
moved the redirectURI processing to the top of the file (c29c7df35905 for those
who forgot), we didn't update the reference to it lower in the file, where
redirectURI was being updated and we expected that to be reflected in the
processing.
The HTTP handler for getting grant codes is now completely tested except for
returning internal errors, which requires a new test harness be built to provoke
internal errors on demand. At this point, however, I'd like to continue on
implementing endpoints.
9 "code.secondbit.org/uuid"
13 getGrantTemplateName = "get_grant"
14 defaultGrantExpiration = 600 // default to ten minute grant expirations
17 // GetGrantHandler presents and processes the page for asking a user to grant access
18 // to their data. See RFC 6749, Section 4.1.
19 func GetGrantHandler(w http.ResponseWriter, r *http.Request, context Context) {
20 if r.URL.Query().Get("client_id") == "" {
21 w.WriteHeader(http.StatusBadRequest)
22 context.Render(w, getGrantTemplateName, map[string]interface{}{
23 "error": template.HTML("Client ID must be specified in the request."),
27 clientID, err := uuid.Parse(r.URL.Query().Get("client_id"))
29 w.WriteHeader(http.StatusBadRequest)
30 context.Render(w, getGrantTemplateName, map[string]interface{}{
31 "error": template.HTML("client_id is not a valid Client ID."),
35 redirectURI := r.URL.Query().Get("redirect_uri")
36 redirectURL, err := url.Parse(redirectURI)
38 w.WriteHeader(http.StatusBadRequest)
39 context.Render(w, getGrantTemplateName, map[string]interface{}{
40 "error": template.HTML("The redirect_uri specified is not valid."),
44 client, err := context.GetClient(clientID)
46 if err == ErrClientNotFound {
47 w.WriteHeader(http.StatusBadRequest)
48 context.Render(w, getGrantTemplateName, map[string]interface{}{
49 "error": template.HTML("The specified Client couldn’t be found."),
52 w.WriteHeader(http.StatusInternalServerError)
53 context.Render(w, getGrantTemplateName, map[string]interface{}{
54 "internal_error": template.HTML(err.Error()),
59 // whether a redirect URI is valid or not depends on the number of endpoints
60 // the client has registered
61 numEndpoints, err := context.CountEndpoints(clientID)
63 w.WriteHeader(http.StatusInternalServerError)
64 context.Render(w, getGrantTemplateName, map[string]interface{}{
65 "internal_error": template.HTML(err.Error()),
70 if redirectURI != "" {
71 // BUG(paddy): We really should normalize URIs before trying to compare them.
72 validURI, err = context.CheckEndpoint(clientID, redirectURI)
74 w.WriteHeader(http.StatusInternalServerError)
75 context.Render(w, getGrantTemplateName, map[string]interface{}{
76 "internal_error": template.HTML(err.Error()),
80 } else if redirectURI == "" && numEndpoints == 1 {
81 // if we don't specify the endpoint and there's only one endpoint, the
82 // request is valid, and we're redirecting to that one endpoint
84 endpoints, err := context.ListEndpoints(clientID, 1, 0)
86 w.WriteHeader(http.StatusInternalServerError)
87 context.Render(w, getGrantTemplateName, map[string]interface{}{
88 "internal_error": template.HTML(err.Error()),
92 if len(endpoints) != 1 {
95 u := endpoints[0].URI // Copy it here to avoid grabbing a pointer to the memstore
96 redirectURI = u.String()
103 w.WriteHeader(http.StatusBadRequest)
104 context.Render(w, getGrantTemplateName, map[string]interface{}{
105 "error": template.HTML("The redirect_uri specified is not valid."),
109 scope := r.URL.Query().Get("scope")
110 state := r.URL.Query().Get("state")
111 if r.URL.Query().Get("response_type") != "code" {
112 q := redirectURL.Query()
113 q.Add("error", "invalid_request")
114 q.Add("state", state)
115 redirectURL.RawQuery = q.Encode()
116 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
119 if r.Method == "POST" {
120 // BUG(paddy): We need to implement CSRF protection when obtaining a grant code.
121 if r.PostFormValue("grant") == "approved" {
122 code := uuid.NewID().String()
126 ExpiresIn: defaultGrantExpiration,
129 RedirectURI: redirectURI,
132 err := context.SaveGrant(grant)
134 q := redirectURL.Query()
135 q.Add("error", "server_error")
136 q.Add("state", state)
137 redirectURL.RawQuery = q.Encode()
138 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
141 q := redirectURL.Query()
143 q.Add("state", state)
144 redirectURL.RawQuery = q.Encode()
145 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
148 q := redirectURL.Query()
149 q.Add("error", "access_denied")
150 q.Add("state", state)
151 redirectURL.RawQuery = q.Encode()
152 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
155 w.WriteHeader(http.StatusOK)
156 context.Render(w, getGrantTemplateName, map[string]interface{}{