auth

Paddy 2014-11-02 Parent:cef5111af5c7 Child:c29c7df35905

63:dd75d24475c0 Go to Latest

auth/http.go

Turn our TODO into a BUG. Lack of CSRF protection is most decidedly a bug, so let's list it as such.

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 client, err := context.GetClient(clientID)
36 if err != nil {
37 if err == ErrClientNotFound {
38 w.WriteHeader(http.StatusBadRequest)
39 context.Render(w, getGrantTemplateName, map[string]interface{}{
40 "error": template.HTML("The specified Client couldn’t be found."),
41 })
42 } else {
43 w.WriteHeader(http.StatusInternalServerError)
44 context.Render(w, getGrantTemplateName, map[string]interface{}{
45 "internal_error": template.HTML(err.Error()),
46 })
47 }
48 return
49 }
50 // whether a redirect URI is valid or not depends on the number of endpoints
51 // the client has registered
52 numEndpoints, err := context.CountEndpoints(clientID)
53 if err != nil {
54 w.WriteHeader(http.StatusInternalServerError)
55 context.Render(w, getGrantTemplateName, map[string]interface{}{
56 "internal_error": template.HTML(err.Error()),
57 })
58 return
59 }
60 redirectURI := r.URL.Query().Get("redirect_uri")
61 var validURI bool
62 if redirectURI != "" {
63 // BUG(paddy): We really should normalize URIs before trying to compare them.
64 validURI, err = context.CheckEndpoint(clientID, redirectURI)
65 if err != nil {
66 w.WriteHeader(http.StatusInternalServerError)
67 context.Render(w, getGrantTemplateName, map[string]interface{}{
68 "internal_error": template.HTML(err.Error()),
69 })
70 return
71 }
72 } else if redirectURI == "" && numEndpoints == 1 {
73 // if we don't specify the endpoint and there's only one endpoint, the
74 // request is valid, and we're redirecting to that one endpoint
75 validURI = true
76 endpoints, err := context.ListEndpoints(clientID, 1, 0)
77 if err != nil {
78 w.WriteHeader(http.StatusInternalServerError)
79 context.Render(w, getGrantTemplateName, map[string]interface{}{
80 "internal_error": template.HTML(err.Error()),
81 })
82 return
83 }
84 if len(endpoints) != 1 {
85 validURI = false
86 } else {
87 redirectURI = endpoints[0].URI.String()
88 }
89 } else {
90 validURI = false
91 }
92 if !validURI {
93 w.WriteHeader(http.StatusBadRequest)
94 context.Render(w, getGrantTemplateName, map[string]interface{}{
95 "error": template.HTML("The redirect_uri specified is not valid."),
96 })
97 return
98 }
99 scope := r.URL.Query().Get("scope")
100 state := r.URL.Query().Get("state")
101 redirectURL, err := url.Parse(redirectURI)
102 if err != nil {
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 if r.URL.Query().Get("response_type") != "code" {
110 redirectURL.Query().Add("error", "invalid_request")
111 redirectURL.Query().Add("state", state)
112 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
113 return
114 }
115 if r.Method == "POST" {
116 // BUG(paddy): We need to implement CSRF protection when obtaining a grant code.
117 if r.PostFormValue("grant") == "approved" {
118 code := uuid.NewID().String()
119 grant := Grant{
120 Code: code,
121 Created: time.Now(),
122 ExpiresIn: defaultGrantExpiration,
123 ClientID: clientID,
124 Scope: scope,
125 RedirectURI: redirectURI,
126 State: state,
127 }
128 err := context.SaveGrant(grant)
129 if err != nil {
130 redirectURL.Query().Add("error", "server_error")
131 redirectURL.Query().Add("state", state)
132 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
133 return
134 }
135 redirectURL.Query().Add("code", code)
136 redirectURL.Query().Add("state", state)
137 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
138 return
139 }
140 redirectURL.Query().Add("error", "access_denied")
141 redirectURL.Query().Add("state", state)
142 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
143 return
144 }
145 w.WriteHeader(http.StatusOK)
146 context.Render(w, getGrantTemplateName, map[string]interface{}{
147 "client": client,
148 })
149 }