auth

Paddy 2014-11-03 Parent:f97ca45d5657 Child:752c2fb9731c

66:55d5107e8805 Go to Latest

auth/http.go

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.

History
1 package auth
3 import (
4 "html/template"
5 "net/http"
6 "net/url"
7 "time"
9 "code.secondbit.org/uuid"
10 )
12 const (
13 getGrantTemplateName = "get_grant"
14 defaultGrantExpiration = 600 // default to ten minute grant expirations
15 )
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."),
24 })
25 return
26 }
27 clientID, err := uuid.Parse(r.URL.Query().Get("client_id"))
28 if err != nil {
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."),
32 })
33 return
34 }
35 redirectURI := r.URL.Query().Get("redirect_uri")
36 redirectURL, err := url.Parse(redirectURI)
37 if err != nil {
38 w.WriteHeader(http.StatusBadRequest)
39 context.Render(w, getGrantTemplateName, map[string]interface{}{
40 "error": template.HTML("The redirect_uri specified is not valid."),
41 })
42 return
43 }
44 client, err := context.GetClient(clientID)
45 if err != nil {
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."),
50 })
51 } else {
52 w.WriteHeader(http.StatusInternalServerError)
53 context.Render(w, getGrantTemplateName, map[string]interface{}{
54 "internal_error": template.HTML(err.Error()),
55 })
56 }
57 return
58 }
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)
62 if err != nil {
63 w.WriteHeader(http.StatusInternalServerError)
64 context.Render(w, getGrantTemplateName, map[string]interface{}{
65 "internal_error": template.HTML(err.Error()),
66 })
67 return
68 }
69 var validURI bool
70 if redirectURI != "" {
71 // BUG(paddy): We really should normalize URIs before trying to compare them.
72 validURI, err = context.CheckEndpoint(clientID, redirectURI)
73 if err != nil {
74 w.WriteHeader(http.StatusInternalServerError)
75 context.Render(w, getGrantTemplateName, map[string]interface{}{
76 "internal_error": template.HTML(err.Error()),
77 })
78 return
79 }
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
83 validURI = true
84 endpoints, err := context.ListEndpoints(clientID, 1, 0)
85 if err != nil {
86 w.WriteHeader(http.StatusInternalServerError)
87 context.Render(w, getGrantTemplateName, map[string]interface{}{
88 "internal_error": template.HTML(err.Error()),
89 })
90 return
91 }
92 if len(endpoints) != 1 {
93 validURI = false
94 } else {
95 u := endpoints[0].URI // Copy it here to avoid grabbing a pointer to the memstore
96 redirectURI = u.String()
97 redirectURL = &u
98 }
99 } else {
100 validURI = false
101 }
102 if !validURI {
103 w.WriteHeader(http.StatusBadRequest)
104 context.Render(w, getGrantTemplateName, map[string]interface{}{
105 "error": template.HTML("The redirect_uri specified is not valid."),
106 })
107 return
108 }
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)
117 return
118 }
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()
123 grant := Grant{
124 Code: code,
125 Created: time.Now(),
126 ExpiresIn: defaultGrantExpiration,
127 ClientID: clientID,
128 Scope: scope,
129 RedirectURI: redirectURI,
130 State: state,
131 }
132 err := context.SaveGrant(grant)
133 if err != nil {
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)
139 return
140 }
141 q := redirectURL.Query()
142 q.Add("code", code)
143 q.Add("state", state)
144 redirectURL.RawQuery = q.Encode()
145 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
146 return
147 }
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)
153 return
154 }
155 w.WriteHeader(http.StatusOK)
156 context.Render(w, getGrantTemplateName, map[string]interface{}{
157 "client": client,
158 })
159 }