auth

Paddy 2014-11-02 Parent:c29c7df35905 Child:55d5107e8805

65:f97ca45d5657 Go to Latest

auth/http.go

Fix bug with response_type redirect, add tests. Test that we redirect with an error when an invalid response_type is supplied. Fix a bug that would not add any of our parameters to the redirect URL.

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@56 95 redirectURI = endpoints[0].URI.String()
paddy@56 96 }
paddy@56 97 } else {
paddy@56 98 validURI = false
paddy@56 99 }
paddy@56 100 if !validURI {
paddy@56 101 w.WriteHeader(http.StatusBadRequest)
paddy@56 102 context.Render(w, getGrantTemplateName, map[string]interface{}{
paddy@61 103 "error": template.HTML("The redirect_uri specified is not valid."),
paddy@56 104 })
paddy@56 105 return
paddy@56 106 }
paddy@60 107 scope := r.URL.Query().Get("scope")
paddy@60 108 state := r.URL.Query().Get("state")
paddy@56 109 if r.URL.Query().Get("response_type") != "code" {
paddy@65 110 q := redirectURL.Query()
paddy@65 111 q.Add("error", "invalid_request")
paddy@65 112 q.Add("state", state)
paddy@65 113 redirectURL.RawQuery = q.Encode()
paddy@60 114 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
paddy@60 115 return
paddy@56 116 }
paddy@56 117 if r.Method == "POST" {
paddy@63 118 // BUG(paddy): We need to implement CSRF protection when obtaining a grant code.
paddy@56 119 if r.PostFormValue("grant") == "approved" {
paddy@60 120 code := uuid.NewID().String()
paddy@60 121 grant := Grant{
paddy@60 122 Code: code,
paddy@60 123 Created: time.Now(),
paddy@60 124 ExpiresIn: defaultGrantExpiration,
paddy@60 125 ClientID: clientID,
paddy@60 126 Scope: scope,
paddy@60 127 RedirectURI: redirectURI,
paddy@60 128 State: state,
paddy@60 129 }
paddy@60 130 err := context.SaveGrant(grant)
paddy@60 131 if err != nil {
paddy@60 132 redirectURL.Query().Add("error", "server_error")
paddy@60 133 redirectURL.Query().Add("state", state)
paddy@60 134 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
paddy@60 135 return
paddy@60 136 }
paddy@60 137 redirectURL.Query().Add("code", code)
paddy@60 138 redirectURL.Query().Add("state", state)
paddy@60 139 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
paddy@60 140 return
paddy@56 141 }
paddy@60 142 redirectURL.Query().Add("error", "access_denied")
paddy@60 143 redirectURL.Query().Add("state", state)
paddy@60 144 http.Redirect(w, r, redirectURL.String(), http.StatusFound)
paddy@60 145 return
paddy@56 146 }
paddy@51 147 w.WriteHeader(http.StatusOK)
paddy@56 148 context.Render(w, getGrantTemplateName, map[string]interface{}{
paddy@56 149 "client": client,
paddy@56 150 })
paddy@51 151 }