auth

Paddy 2014-11-03 Parent:f97ca45d5657 Child:752c2fb9731c

66:55d5107e8805 Go to Latest

auth/http.go

Bugfixes and tests for getting grants. Add tests for the grant confirmation part of the request to get a grant code. When the request is denied, the redirect should have an access_denied error. When the request is approved, the redirect should contain a code. Fix numerous bugs in which the redirect URL didn't contain the parameters we thought it would. Basically, anything that still used req.URL.Query().Set() instead of copying the query, modifying it, and setting req.URL.RawQuery. Fix a bug in which the redirectURL wasn't being properly set. Basically, when we moved the redirectURI processing to the top of the file (c29c7df35905 for those who forgot), we didn't update the reference to it lower in the file, where redirectURI was being updated and we expected that to be reflected in the processing. The HTTP handler for getting grant codes is now completely tested except for returning internal errors, which requires a new test harness be built to provoke internal errors on demand. At this point, however, I'd like to continue on implementing endpoints.

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