auth

Paddy 2014-11-09 Parent:55d5107e8805 Child:42bc3e44f4fe

68:752c2fb9731c Go to Latest

auth/http.go

Add TODOs. There's still a lot of the OAuth2 spec to implement, but we're making good progress. Let's keep track of the remaining pieces using TODO markers.

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 }
161 // TODO(paddy): exchange code for access token
162 // TODO(paddy): exchange user credentials for access token
163 // TODO(paddy): exchange client credentials for access token
164 // TODO(paddy): implicit grant for access token
165 // TODO(paddy): exchange refresh token for access token