auth
auth/http.go
Fix bug with response_type redirect, add tests. Test that we redirect with an error when an invalid response_type is supplied. Fix a bug that would not add any of our parameters to the redirect URL.
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 redirectURI = endpoints[0].URI.String()
96 }
97 } else {
98 validURI = false
99 }
100 if !validURI {
101 w.WriteHeader(http.StatusBadRequest)
102 context.Render(w, getGrantTemplateName, map[string]interface{}{
103 "error": template.HTML("The redirect_uri specified is not valid."),
104 })
105 return
106 }
107 scope := r.URL.Query().Get("scope")
108 state := r.URL.Query().Get("state")
109 if r.URL.Query().Get("response_type") != "code" {
110 q := redirectURL.Query()
111 q.Add("error", "invalid_request")
112 q.Add("state", state)
113 redirectURL.RawQuery = q.Encode()
114 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
115 return
116 }
117 if r.Method == "POST" {
118 // BUG(paddy): We need to implement CSRF protection when obtaining a grant code.
119 if r.PostFormValue("grant") == "approved" {
120 code := uuid.NewID().String()
121 grant := Grant{
122 Code: code,
123 Created: time.Now(),
124 ExpiresIn: defaultGrantExpiration,
125 ClientID: clientID,
126 Scope: scope,
127 RedirectURI: redirectURI,
128 State: state,
129 }
130 err := context.SaveGrant(grant)
131 if err != nil {
132 redirectURL.Query().Add("error", "server_error")
133 redirectURL.Query().Add("state", state)
134 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
135 return
136 }
137 redirectURL.Query().Add("code", code)
138 redirectURL.Query().Add("state", state)
139 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
140 return
141 }
142 redirectURL.Query().Add("error", "access_denied")
143 redirectURL.Query().Add("state", state)
144 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
145 return
146 }
147 w.WriteHeader(http.StatusOK)
148 context.Render(w, getGrantTemplateName, map[string]interface{}{
149 "client": client,
150 })
151 }