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