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