api
2015-12-14
Parent:57c9412e8000
api/api.go
Update scope helpers and add access denied error. Refactor CheckScopes to take the Scopes as input instead of reading them from the passed request. Also, surface a GetScopes method that will pull the Scopes from the request and return them. While we're at it, we refactored to use the sort package instead of a manual search. We also needed an access_denied error, and that will come up a lot, so it was worth making a constant, just like our invalid_format error.
| paddy@0 | 1 package api |
| paddy@0 | 2 |
| paddy@0 | 3 import ( |
| paddy@0 | 4 "encoding/json" |
| paddy@0 | 5 "errors" |
| paddy@0 | 6 "log" |
| paddy@0 | 7 "net/http" |
| paddy@2 | 8 "sort" |
| paddy@0 | 9 "strings" |
| paddy@0 | 10 |
| paddy@0 | 11 "bitbucket.org/ww/goautoneg" |
| paddy@0 | 12 |
| paddy@0 | 13 "code.secondbit.org/uuid.hg" |
| paddy@0 | 14 |
| paddy@0 | 15 "golang.org/x/net/context" |
| paddy@0 | 16 ) |
| paddy@0 | 17 |
| paddy@0 | 18 const ( |
| paddy@0 | 19 RequestErrAccessDenied = "access_denied" |
| paddy@0 | 20 RequestErrInsufficient = "insufficient" |
| paddy@0 | 21 RequestErrOverflow = "overflow" |
| paddy@0 | 22 RequestErrInvalidValue = "invalid_value" |
| paddy@0 | 23 RequestErrInvalidFormat = "invalid_format" |
| paddy@0 | 24 RequestErrMissing = "missing" |
| paddy@0 | 25 RequestErrNotFound = "not_found" |
| paddy@0 | 26 RequestErrConflict = "conflict" |
| paddy@0 | 27 RequestErrActOfGod = "act_of_god" |
| paddy@0 | 28 ) |
| paddy@0 | 29 |
| paddy@0 | 30 var ( |
| paddy@0 | 31 ActOfGodError = []RequestError{{Slug: RequestErrActOfGod}} |
| paddy@0 | 32 InvalidFormatError = []RequestError{{Slug: RequestErrInvalidFormat, Field: "/"}} |
| paddy@2 | 33 AccessDeniedError = []RequestError{{Slug: RequestErrAccessDenied}} |
| paddy@0 | 34 |
| paddy@0 | 35 Encoders = []string{"application/json"} |
| paddy@0 | 36 |
| paddy@0 | 37 ErrUserIDNotSet = errors.New("user ID not set") |
| paddy@0 | 38 ) |
| paddy@0 | 39 |
| paddy@0 | 40 type RequestError struct { |
| paddy@0 | 41 Slug string `json:"error,omitempty"` |
| paddy@0 | 42 Field string `json:"field,omitempty"` |
| paddy@0 | 43 Param string `json:"param,omitempty"` |
| paddy@0 | 44 Header string `json:"header,omitempty"` |
| paddy@0 | 45 } |
| paddy@0 | 46 |
| paddy@1 | 47 type ContextHandler func(context.Context, http.ResponseWriter, *http.Request) |
| paddy@0 | 48 |
| paddy@0 | 49 func NegotiateMiddleware(h http.Handler) http.Handler { |
| paddy@0 | 50 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| paddy@0 | 51 if r.Header.Get("Accept") != "" { |
| paddy@0 | 52 contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders) |
| paddy@0 | 53 if contentType == "" { |
| paddy@0 | 54 w.WriteHeader(http.StatusNotAcceptable) |
| paddy@0 | 55 w.Write([]byte("Unsupported content type requested: " + r.Header.Get("Accept"))) |
| paddy@0 | 56 return |
| paddy@0 | 57 } |
| paddy@0 | 58 } |
| paddy@0 | 59 h.ServeHTTP(w, r) |
| paddy@0 | 60 }) |
| paddy@0 | 61 } |
| paddy@0 | 62 |
| paddy@0 | 63 func Encode(w http.ResponseWriter, r *http.Request, status int, resp interface{}) { |
| paddy@0 | 64 contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders) |
| paddy@0 | 65 w.Header().Set("content-type", contentType) |
| paddy@0 | 66 w.WriteHeader(status) |
| paddy@0 | 67 var err error |
| paddy@0 | 68 switch contentType { |
| paddy@0 | 69 case "application/json": |
| paddy@0 | 70 enc := json.NewEncoder(w) |
| paddy@0 | 71 err = enc.Encode(resp) |
| paddy@0 | 72 default: |
| paddy@0 | 73 enc := json.NewEncoder(w) |
| paddy@0 | 74 err = enc.Encode(resp) |
| paddy@0 | 75 } |
| paddy@0 | 76 if err != nil { |
| paddy@0 | 77 log.Println(err) |
| paddy@0 | 78 } |
| paddy@0 | 79 } |
| paddy@0 | 80 |
| paddy@0 | 81 func Decode(r *http.Request, target interface{}) error { |
| paddy@0 | 82 defer r.Body.Close() |
| paddy@0 | 83 switch r.Header.Get("Content-Type") { |
| paddy@0 | 84 case "application/json": |
| paddy@0 | 85 dec := json.NewDecoder(r.Body) |
| paddy@0 | 86 return dec.Decode(target) |
| paddy@0 | 87 default: |
| paddy@0 | 88 dec := json.NewDecoder(r.Body) |
| paddy@0 | 89 return dec.Decode(target) |
| paddy@0 | 90 } |
| paddy@0 | 91 } |
| paddy@0 | 92 |
| paddy@0 | 93 func CORSMiddleware(h http.Handler) http.Handler { |
| paddy@0 | 94 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| paddy@0 | 95 w.Header().Set("Access-Control-Allow-Origin", "*") |
| paddy@0 | 96 w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers")) |
| paddy@0 | 97 w.Header().Set("Access-Control-Allow-Credentials", "true") |
| paddy@0 | 98 if strings.ToLower(r.Method) == "options" { |
| paddy@0 | 99 methods := strings.Join(r.Header[http.CanonicalHeaderKey("Trout-Methods")], ", ") |
| paddy@0 | 100 w.Header().Set("Access-Control-Allow-Methods", methods) |
| paddy@0 | 101 w.Header().Set("Allow", methods) |
| paddy@0 | 102 w.WriteHeader(http.StatusOK) |
| paddy@0 | 103 return |
| paddy@0 | 104 } |
| paddy@0 | 105 h.ServeHTTP(w, r) |
| paddy@0 | 106 }) |
| paddy@0 | 107 } |
| paddy@0 | 108 |
| paddy@0 | 109 func ContextWrapper(c context.Context, handler ContextHandler) http.Handler { |
| paddy@0 | 110 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| paddy@1 | 111 handler(c, w, r) |
| paddy@0 | 112 }) |
| paddy@0 | 113 } |
| paddy@0 | 114 |
| paddy@2 | 115 func CheckScopes(scopes []string, checking ...string) bool { |
| paddy@2 | 116 sort.Strings(scopes) |
| paddy@2 | 117 for _, scope := range checking { |
| paddy@2 | 118 found := sort.SearchStrings(scopes, scope) |
| paddy@2 | 119 if found == len(scopes) || scopes[found] != scope { |
| paddy@0 | 120 return false |
| paddy@0 | 121 } |
| paddy@0 | 122 } |
| paddy@0 | 123 return true |
| paddy@0 | 124 } |
| paddy@0 | 125 |
| paddy@2 | 126 func GetScopes(r *http.Request) []string { |
| paddy@2 | 127 scopes := strings.Split(r.Header.Get("scopes"), " ") |
| paddy@2 | 128 for pos, scope := range scopes { |
| paddy@2 | 129 scopes[pos] = strings.TrimSpace(scope) |
| paddy@2 | 130 } |
| paddy@2 | 131 sort.Strings(scopes) |
| paddy@2 | 132 return scopes |
| paddy@2 | 133 } |
| paddy@2 | 134 |
| paddy@0 | 135 func AuthUser(r *http.Request) (uuid.ID, error) { |
| paddy@0 | 136 rawID := r.Header.Get("User-ID") |
| paddy@0 | 137 if rawID == "" { |
| paddy@0 | 138 return nil, ErrUserIDNotSet |
| paddy@0 | 139 } |
| paddy@0 | 140 return uuid.Parse(rawID) |
| paddy@0 | 141 } |