1// Package api implements an HTTP-based API and server for CFSSL. 2package api 3 4import ( 5 "encoding/json" 6 "io/ioutil" 7 "net/http" 8 9 "github.com/cloudflare/cfssl/errors" 10 "github.com/cloudflare/cfssl/log" 11) 12 13// Handler is an interface providing a generic mechanism for handling HTTP requests. 14type Handler interface { 15 Handle(w http.ResponseWriter, r *http.Request) error 16} 17 18// HTTPHandler is a wrapper that encapsulates Handler interface as http.Handler. 19// HTTPHandler also enforces that the Handler only responds to requests with registered HTTP methods. 20type HTTPHandler struct { 21 Handler // CFSSL handler 22 Methods []string // The associated HTTP methods 23} 24 25// HandlerFunc is similar to the http.HandlerFunc type; it serves as 26// an adapter allowing the use of ordinary functions as Handlers. If 27// f is a function with the appropriate signature, HandlerFunc(f) is a 28// Handler object that calls f. 29type HandlerFunc func(http.ResponseWriter, *http.Request) error 30 31// Handle calls f(w, r) 32func (f HandlerFunc) Handle(w http.ResponseWriter, r *http.Request) error { 33 w.Header().Set("Content-Type", "application/json") 34 return f(w, r) 35} 36 37// HandleError is the centralised error handling and reporting. 38func HandleError(w http.ResponseWriter, err error) (code int) { 39 if err == nil { 40 return http.StatusOK 41 } 42 msg := err.Error() 43 httpCode := http.StatusInternalServerError 44 45 // If it is recognized as HttpError emitted from cfssl, 46 // we rewrite the status code accordingly. If it is a 47 // cfssl error, set the http status to StatusBadRequest 48 switch err := err.(type) { 49 case *errors.HTTPError: 50 httpCode = err.StatusCode 51 code = err.StatusCode 52 case *errors.Error: 53 httpCode = http.StatusBadRequest 54 code = err.ErrorCode 55 msg = err.Message 56 } 57 58 response := NewErrorResponse(msg, code) 59 jsonMessage, err := json.Marshal(response) 60 if err != nil { 61 log.Errorf("Failed to marshal JSON: %v", err) 62 } else { 63 msg = string(jsonMessage) 64 } 65 http.Error(w, msg, httpCode) 66 return code 67} 68 69// ServeHTTP encapsulates the call to underlying Handler to handle the request 70// and return the response with proper HTTP status code 71func (h HTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 72 var err error 73 var match bool 74 // Throw 405 when requested with an unsupported verb. 75 for _, m := range h.Methods { 76 if m == r.Method { 77 match = true 78 } 79 } 80 if match { 81 err = h.Handle(w, r) 82 } else { 83 err = errors.NewMethodNotAllowed(r.Method) 84 } 85 status := HandleError(w, err) 86 log.Infof("%s - \"%s %s\" %d", r.RemoteAddr, r.Method, r.URL, status) 87} 88 89// readRequestBlob takes a JSON-blob-encoded response body in the form 90// map[string]string and returns it, the list of keywords presented, 91// and any error that occurred. 92func readRequestBlob(r *http.Request) (map[string]string, error) { 93 var blob map[string]string 94 95 body, err := ioutil.ReadAll(r.Body) 96 if err != nil { 97 return nil, err 98 } 99 r.Body.Close() 100 101 err = json.Unmarshal(body, &blob) 102 if err != nil { 103 return nil, err 104 } 105 return blob, nil 106} 107 108// ProcessRequestOneOf reads a JSON blob for the request and makes 109// sure it contains one of a set of keywords. For example, a request 110// might have the ('foo' && 'bar') keys, OR it might have the 'baz' 111// key. In either case, we want to accept the request; however, if 112// none of these sets shows up, the request is a bad request, and it 113// should be returned. 114func ProcessRequestOneOf(r *http.Request, keywordSets [][]string) (map[string]string, []string, error) { 115 blob, err := readRequestBlob(r) 116 if err != nil { 117 return nil, nil, err 118 } 119 120 var matched []string 121 for _, set := range keywordSets { 122 if matchKeywords(blob, set) { 123 if matched != nil { 124 return nil, nil, errors.NewBadRequestString("mismatched parameters") 125 } 126 matched = set 127 } 128 } 129 if matched == nil { 130 return nil, nil, errors.NewBadRequestString("no valid parameter sets found") 131 } 132 return blob, matched, nil 133} 134 135// ProcessRequestFirstMatchOf reads a JSON blob for the request and returns 136// the first match of a set of keywords. For example, a request 137// might have one of the following combinations: (foo=1, bar=2), (foo=1), and (bar=2) 138// By giving a specific ordering of those combinations, we could decide how to accept 139// the request. 140func ProcessRequestFirstMatchOf(r *http.Request, keywordSets [][]string) (map[string]string, []string, error) { 141 blob, err := readRequestBlob(r) 142 if err != nil { 143 return nil, nil, err 144 } 145 146 for _, set := range keywordSets { 147 if matchKeywords(blob, set) { 148 return blob, set, nil 149 } 150 } 151 return nil, nil, errors.NewBadRequestString("no valid parameter sets found") 152} 153 154func matchKeywords(blob map[string]string, keywords []string) bool { 155 for _, keyword := range keywords { 156 if _, ok := blob[keyword]; !ok { 157 return false 158 } 159 } 160 return true 161} 162 163// ResponseMessage implements the standard for response errors and 164// messages. A message has a code and a string message. 165type ResponseMessage struct { 166 Code int `json:"code"` 167 Message string `json:"message"` 168} 169 170// Response implements the CloudFlare standard for API 171// responses. 172type Response struct { 173 Success bool `json:"success"` 174 Result interface{} `json:"result"` 175 Errors []ResponseMessage `json:"errors"` 176 Messages []ResponseMessage `json:"messages"` 177} 178 179// NewSuccessResponse is a shortcut for creating new successul API 180// responses. 181func NewSuccessResponse(result interface{}) Response { 182 return Response{ 183 Success: true, 184 Result: result, 185 Errors: []ResponseMessage{}, 186 Messages: []ResponseMessage{}, 187 } 188} 189 190// NewSuccessResponseWithMessage is a shortcut for creating new successul API 191// responses that includes a message. 192func NewSuccessResponseWithMessage(result interface{}, message string, code int) Response { 193 return Response{ 194 Success: true, 195 Result: result, 196 Errors: []ResponseMessage{}, 197 Messages: []ResponseMessage{{code, message}}, 198 } 199} 200 201// NewErrorResponse is a shortcut for creating an error response for a 202// single error. 203func NewErrorResponse(message string, code int) Response { 204 return Response{ 205 Success: false, 206 Result: nil, 207 Errors: []ResponseMessage{{code, message}}, 208 Messages: []ResponseMessage{}, 209 } 210} 211 212// SendResponse builds a response from the result, sets the JSON 213// header, and writes to the http.ResponseWriter. 214func SendResponse(w http.ResponseWriter, result interface{}) error { 215 response := NewSuccessResponse(result) 216 w.Header().Set("Content-Type", "application/json") 217 enc := json.NewEncoder(w) 218 err := enc.Encode(response) 219 return err 220} 221 222// SendResponseWithMessage builds a response from the result and the 223// provided message, sets the JSON header, and writes to the 224// http.ResponseWriter. 225func SendResponseWithMessage(w http.ResponseWriter, result interface{}, message string, code int) error { 226 response := NewSuccessResponseWithMessage(result, message, code) 227 w.Header().Set("Content-Type", "application/json") 228 enc := json.NewEncoder(w) 229 err := enc.Encode(response) 230 return err 231} 232