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.
1 package api
3 import (
4 "encoding/json"
5 "errors"
6 "log"
7 "net/http"
8 "sort"
9 "strings"
11 "bitbucket.org/ww/goautoneg"
13 "code.secondbit.org/uuid.hg"
15 "golang.org/x/net/context"
16 )
18 const (
19 RequestErrAccessDenied = "access_denied"
20 RequestErrInsufficient = "insufficient"
21 RequestErrOverflow = "overflow"
22 RequestErrInvalidValue = "invalid_value"
23 RequestErrInvalidFormat = "invalid_format"
24 RequestErrMissing = "missing"
25 RequestErrNotFound = "not_found"
26 RequestErrConflict = "conflict"
27 RequestErrActOfGod = "act_of_god"
28 )
30 var (
31 ActOfGodError = []RequestError{{Slug: RequestErrActOfGod}}
32 InvalidFormatError = []RequestError{{Slug: RequestErrInvalidFormat, Field: "/"}}
33 AccessDeniedError = []RequestError{{Slug: RequestErrAccessDenied}}
35 Encoders = []string{"application/json"}
37 ErrUserIDNotSet = errors.New("user ID not set")
38 )
40 type RequestError struct {
41 Slug string `json:"error,omitempty"`
42 Field string `json:"field,omitempty"`
43 Param string `json:"param,omitempty"`
44 Header string `json:"header,omitempty"`
45 }
47 type ContextHandler func(context.Context, http.ResponseWriter, *http.Request)
49 func NegotiateMiddleware(h http.Handler) http.Handler {
50 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
51 if r.Header.Get("Accept") != "" {
52 contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders)
53 if contentType == "" {
54 w.WriteHeader(http.StatusNotAcceptable)
55 w.Write([]byte("Unsupported content type requested: " + r.Header.Get("Accept")))
56 return
57 }
58 }
59 h.ServeHTTP(w, r)
60 })
61 }
63 func Encode(w http.ResponseWriter, r *http.Request, status int, resp interface{}) {
64 contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders)
65 w.Header().Set("content-type", contentType)
66 w.WriteHeader(status)
67 var err error
68 switch contentType {
69 case "application/json":
70 enc := json.NewEncoder(w)
71 err = enc.Encode(resp)
72 default:
73 enc := json.NewEncoder(w)
74 err = enc.Encode(resp)
75 }
76 if err != nil {
77 log.Println(err)
78 }
79 }
81 func Decode(r *http.Request, target interface{}) error {
82 defer r.Body.Close()
83 switch r.Header.Get("Content-Type") {
84 case "application/json":
85 dec := json.NewDecoder(r.Body)
86 return dec.Decode(target)
87 default:
88 dec := json.NewDecoder(r.Body)
89 return dec.Decode(target)
90 }
91 }
93 func CORSMiddleware(h http.Handler) http.Handler {
94 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
95 w.Header().Set("Access-Control-Allow-Origin", "*")
96 w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
97 w.Header().Set("Access-Control-Allow-Credentials", "true")
98 if strings.ToLower(r.Method) == "options" {
99 methods := strings.Join(r.Header[http.CanonicalHeaderKey("Trout-Methods")], ", ")
100 w.Header().Set("Access-Control-Allow-Methods", methods)
101 w.Header().Set("Allow", methods)
102 w.WriteHeader(http.StatusOK)
103 return
104 }
105 h.ServeHTTP(w, r)
106 })
107 }
109 func ContextWrapper(c context.Context, handler ContextHandler) http.Handler {
110 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
111 handler(c, w, r)
112 })
113 }
115 func CheckScopes(scopes []string, checking ...string) bool {
116 sort.Strings(scopes)
117 for _, scope := range checking {
118 found := sort.SearchStrings(scopes, scope)
119 if found == len(scopes) || scopes[found] != scope {
120 return false
121 }
122 }
123 return true
124 }
126 func GetScopes(r *http.Request) []string {
127 scopes := strings.Split(r.Header.Get("scopes"), " ")
128 for pos, scope := range scopes {
129 scopes[pos] = strings.TrimSpace(scope)
130 }
131 sort.Strings(scopes)
132 return scopes
133 }
135 func AuthUser(r *http.Request) (uuid.ID, error) {
136 rawID := r.Header.Get("User-ID")
137 if rawID == "" {
138 return nil, ErrUserIDNotSet
139 }
140 return uuid.Parse(rawID)
141 }