auth
auth/http.go
Require full URLs for Endpoints. The spec says that we SHOULD require full URLs for redirection, but we _can_ offer the ability to set a URL as a "partial URL" if we really must. I see no particular reason to do this, so I've simplified the code by pulling that option out. This means that URLs (as long as they're normalized, which I've filed a bug in the codebase to do) can be checked using simple string comparison, which makes the likelihood of bugs across clientStorage implementations a lot lower.
| paddy@51 | 1 package auth |
| paddy@51 | 2 |
| paddy@51 | 3 import ( |
| paddy@51 | 4 "net/http" |
| paddy@56 | 5 |
| paddy@56 | 6 "code.secondbit.org/uuid" |
| paddy@51 | 7 ) |
| paddy@51 | 8 |
| paddy@51 | 9 const getGrantTemplateName = "get_grant" |
| paddy@51 | 10 |
| paddy@57 | 11 // GetGrantHandler presents and processes the page for asking a user to grant access |
| paddy@57 | 12 // to their data. See RFC 6749, Section 4.1. |
| paddy@51 | 13 func GetGrantHandler(w http.ResponseWriter, r *http.Request, context Context) { |
| paddy@56 | 14 if r.URL.Query().Get("client_id") == "" { |
| paddy@56 | 15 w.WriteHeader(http.StatusBadRequest) |
| paddy@56 | 16 context.Render(w, getGrantTemplateName, map[string]interface{}{ |
| paddy@56 | 17 "error": "Client ID must be specified in the request.", |
| paddy@56 | 18 }) |
| paddy@56 | 19 return |
| paddy@56 | 20 } |
| paddy@56 | 21 clientID, err := uuid.Parse(r.URL.Query().Get("client_id")) |
| paddy@56 | 22 if err != nil { |
| paddy@56 | 23 w.WriteHeader(http.StatusBadRequest) |
| paddy@56 | 24 context.Render(w, getGrantTemplateName, map[string]interface{}{ |
| paddy@56 | 25 "error": "client_id is not a valid Client ID.", |
| paddy@56 | 26 }) |
| paddy@56 | 27 return |
| paddy@56 | 28 } |
| paddy@56 | 29 client, err := context.GetClient(clientID) |
| paddy@56 | 30 if err != nil { |
| paddy@56 | 31 w.WriteHeader(http.StatusInternalServerError) |
| paddy@56 | 32 context.Render(w, getGrantTemplateName, map[string]interface{}{ |
| paddy@56 | 33 "internal_error": err, |
| paddy@56 | 34 }) |
| paddy@56 | 35 return |
| paddy@56 | 36 } |
| paddy@56 | 37 // whether a redirect URI is valid or not depends on the number of endpoints |
| paddy@56 | 38 // the client has registered |
| paddy@56 | 39 numEndpoints, err := context.CountEndpoints(clientID) |
| paddy@56 | 40 if err != nil { |
| paddy@56 | 41 w.WriteHeader(http.StatusInternalServerError) |
| paddy@56 | 42 context.Render(w, getGrantTemplateName, map[string]interface{}{ |
| paddy@56 | 43 "internal_error": err, |
| paddy@56 | 44 }) |
| paddy@56 | 45 return |
| paddy@56 | 46 } |
| paddy@56 | 47 redirectURI := r.URL.Query().Get("redirect_uri") |
| paddy@56 | 48 var validURI bool |
| paddy@58 | 49 if redirectURI != "" { |
| paddy@58 | 50 // BUG(paddy): We really should normalize URIs before trying to compare them. |
| paddy@58 | 51 validURI, err = context.CheckEndpoint(clientID, redirectURI) |
| 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 } else if redirectURI == "" && numEndpoints == 1 { |
| paddy@56 | 60 // if we don't specify the endpoint and there's only one endpoint, the |
| paddy@56 | 61 // request is valid, and we're redirecting to that one endpoint |
| paddy@56 | 62 validURI = true |
| paddy@56 | 63 endpoints, err := context.ListEndpoints(clientID, 1, 0) |
| 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 if len(endpoints) != 1 { |
| paddy@56 | 72 validURI = false |
| paddy@56 | 73 } else { |
| paddy@56 | 74 redirectURI = endpoints[0].URI.String() |
| paddy@56 | 75 } |
| paddy@56 | 76 } else { |
| paddy@56 | 77 validURI = false |
| paddy@56 | 78 } |
| paddy@56 | 79 if !validURI { |
| paddy@56 | 80 w.WriteHeader(http.StatusBadRequest) |
| paddy@56 | 81 context.Render(w, getGrantTemplateName, map[string]interface{}{ |
| paddy@56 | 82 "error": "The redirect_uri specified is not valid.", |
| paddy@56 | 83 }) |
| paddy@56 | 84 return |
| paddy@56 | 85 } |
| paddy@56 | 86 if r.URL.Query().Get("response_type") != "code" { |
| paddy@56 | 87 // TODO: redirect error |
| paddy@56 | 88 } |
| paddy@56 | 89 //scope := r.URL.Query().Get("scope") |
| paddy@56 | 90 //state := r.URL.Query().Get("state") |
| paddy@56 | 91 if r.Method == "POST" { |
| paddy@56 | 92 // TODO: CSRF protection |
| paddy@56 | 93 if r.PostFormValue("grant") == "approved" { |
| paddy@56 | 94 // TODO: redirect |
| paddy@56 | 95 } else { |
| paddy@56 | 96 // TODO: redirect error |
| paddy@56 | 97 } |
| paddy@56 | 98 } |
| paddy@51 | 99 w.WriteHeader(http.StatusOK) |
| paddy@56 | 100 context.Render(w, getGrantTemplateName, map[string]interface{}{ |
| paddy@56 | 101 "client": client, |
| paddy@56 | 102 }) |
| paddy@51 | 103 } |