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