auth
auth/http.go
Fill out redirects. Actually redirect the user when obtaining a grant. Check if the redirect_uri being passed when obtaining a grant is _actually a URL_. If it's not, return the right error. Add a test for a redirect_uri that isn't a valid URL.
1 package auth
3 import (
4 "net/http"
5 "net/url"
6 "time"
8 "code.secondbit.org/uuid"
9 )
11 const (
12 getGrantTemplateName = "get_grant"
13 defaultGrantExpiration = 600 // default to ten minute grant expirations
14 )
16 // GetGrantHandler presents and processes the page for asking a user to grant access
17 // to their data. See RFC 6749, Section 4.1.
18 func GetGrantHandler(w http.ResponseWriter, r *http.Request, context Context) {
19 if r.URL.Query().Get("client_id") == "" {
20 w.WriteHeader(http.StatusBadRequest)
21 context.Render(w, getGrantTemplateName, map[string]interface{}{
22 "error": "Client ID must be specified in the request.",
23 })
24 return
25 }
26 clientID, err := uuid.Parse(r.URL.Query().Get("client_id"))
27 if err != nil {
28 w.WriteHeader(http.StatusBadRequest)
29 context.Render(w, getGrantTemplateName, map[string]interface{}{
30 "error": "client_id is not a valid Client ID.",
31 })
32 return
33 }
34 client, err := context.GetClient(clientID)
35 if err != nil {
36 if err == ErrClientNotFound {
37 w.WriteHeader(http.StatusBadRequest)
38 context.Render(w, getGrantTemplateName, map[string]interface{}{
39 "error": "The Client specified couldn't be found.",
40 })
41 } else {
42 w.WriteHeader(http.StatusInternalServerError)
43 context.Render(w, getGrantTemplateName, map[string]interface{}{
44 "internal_error": err,
45 })
46 }
47 return
48 }
49 // whether a redirect URI is valid or not depends on the number of endpoints
50 // the client has registered
51 numEndpoints, err := context.CountEndpoints(clientID)
52 if err != nil {
53 w.WriteHeader(http.StatusInternalServerError)
54 context.Render(w, getGrantTemplateName, map[string]interface{}{
55 "internal_error": err,
56 })
57 return
58 }
59 redirectURI := r.URL.Query().Get("redirect_uri")
60 var validURI bool
61 if redirectURI != "" {
62 // BUG(paddy): We really should normalize URIs before trying to compare them.
63 validURI, err = context.CheckEndpoint(clientID, redirectURI)
64 if err != nil {
65 w.WriteHeader(http.StatusInternalServerError)
66 context.Render(w, getGrantTemplateName, map[string]interface{}{
67 "internal_error": err,
68 })
69 return
70 }
71 } else if redirectURI == "" && numEndpoints == 1 {
72 // if we don't specify the endpoint and there's only one endpoint, the
73 // request is valid, and we're redirecting to that one endpoint
74 validURI = true
75 endpoints, err := context.ListEndpoints(clientID, 1, 0)
76 if err != nil {
77 w.WriteHeader(http.StatusInternalServerError)
78 context.Render(w, getGrantTemplateName, map[string]interface{}{
79 "internal_error": err,
80 })
81 return
82 }
83 if len(endpoints) != 1 {
84 validURI = false
85 } else {
86 redirectURI = endpoints[0].URI.String()
87 }
88 } else {
89 validURI = false
90 }
91 if !validURI {
92 w.WriteHeader(http.StatusBadRequest)
93 context.Render(w, getGrantTemplateName, map[string]interface{}{
94 "error": "The redirect_uri specified is not valid.",
95 })
96 return
97 }
98 scope := r.URL.Query().Get("scope")
99 state := r.URL.Query().Get("state")
100 redirectURL, err := url.Parse(redirectURI)
101 if err != nil {
102 w.WriteHeader(http.StatusBadRequest)
103 context.Render(w, getGrantTemplateName, map[string]interface{}{
104 "error": "The redirect_uri specified is not valid.",
105 })
106 return
107 }
108 if r.URL.Query().Get("response_type") != "code" {
109 redirectURL.Query().Add("error", "invalid_request")
110 redirectURL.Query().Add("state", state)
111 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
112 return
113 }
114 if r.Method == "POST" {
115 // TODO: CSRF protection
116 if r.PostFormValue("grant") == "approved" {
117 code := uuid.NewID().String()
118 grant := Grant{
119 Code: code,
120 Created: time.Now(),
121 ExpiresIn: defaultGrantExpiration,
122 ClientID: clientID,
123 Scope: scope,
124 RedirectURI: redirectURI,
125 State: state,
126 }
127 err := context.SaveGrant(grant)
128 if err != nil {
129 redirectURL.Query().Add("error", "server_error")
130 redirectURL.Query().Add("state", state)
131 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
132 return
133 }
134 redirectURL.Query().Add("code", code)
135 redirectURL.Query().Add("state", state)
136 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
137 return
138 }
139 redirectURL.Query().Add("error", "access_denied")
140 redirectURL.Query().Add("state", state)
141 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
142 return
143 }
144 w.WriteHeader(http.StatusOK)
145 context.Render(w, getGrantTemplateName, map[string]interface{}{
146 "client": client,
147 })
148 }