2016-09-26 18:48:55 +00:00
|
|
|
package service
|
2016-09-23 02:41:58 +00:00
|
|
|
|
|
|
|
import (
|
2017-03-15 15:55:30 +00:00
|
|
|
"context"
|
2016-09-23 02:41:58 +00:00
|
|
|
"encoding/json"
|
|
|
|
"net/http"
|
2021-03-26 18:23:29 +00:00
|
|
|
"strconv"
|
2018-06-15 14:13:11 +00:00
|
|
|
|
2021-06-26 04:46:51 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
2021-06-16 17:55:41 +00:00
|
|
|
kithttp "github.com/go-kit/kit/transport/http"
|
2018-06-15 14:13:11 +00:00
|
|
|
"github.com/pkg/errors"
|
2016-09-23 02:41:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// erroer interface is implemented by response structs to encode business logic errors
|
|
|
|
type errorer interface {
|
|
|
|
error() error
|
|
|
|
}
|
|
|
|
|
|
|
|
type jsonError struct {
|
|
|
|
Message string `json:"message"`
|
|
|
|
Errors []map[string]string `json:"errors,omitempty"`
|
2016-12-30 00:40:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// use baseError to encode an jsonError.Errors field with an error that has
|
|
|
|
// a generic "name" field. The frontend client always expects errors in a
|
|
|
|
// []map[string]string format.
|
|
|
|
func baseError(err string) []map[string]string {
|
2021-06-16 17:55:41 +00:00
|
|
|
return []map[string]string{
|
|
|
|
{
|
|
|
|
"name": "base",
|
|
|
|
"reason": err,
|
|
|
|
},
|
2016-12-30 00:40:12 +00:00
|
|
|
}
|
2016-09-23 02:41:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// encode error and status header to the client
|
|
|
|
func encodeError(ctx context.Context, err error, w http.ResponseWriter) {
|
|
|
|
enc := json.NewEncoder(w)
|
|
|
|
enc.SetIndent("", " ")
|
|
|
|
|
|
|
|
type validationError interface {
|
2016-12-30 00:40:12 +00:00
|
|
|
error
|
2016-09-23 02:41:58 +00:00
|
|
|
Invalid() []map[string]string
|
|
|
|
}
|
|
|
|
if e, ok := err.(validationError); ok {
|
|
|
|
ve := jsonError{
|
|
|
|
Message: "Validation Failed",
|
|
|
|
Errors: e.Invalid(),
|
|
|
|
}
|
|
|
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
|
|
|
enc.Encode(ve)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
type permissionError interface {
|
|
|
|
PermissionError() []map[string]string
|
|
|
|
}
|
|
|
|
if e, ok := err.(permissionError); ok {
|
|
|
|
pe := jsonError{
|
|
|
|
Message: "Permission Denied",
|
|
|
|
Errors: e.PermissionError(),
|
|
|
|
}
|
|
|
|
w.WriteHeader(http.StatusForbidden)
|
|
|
|
enc.Encode(pe)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
if fleet.IsForeignKey(errors.Cause(err)) {
|
2018-06-15 14:13:11 +00:00
|
|
|
ve := jsonError{
|
|
|
|
Message: "Validation Failed",
|
|
|
|
Errors: baseError(err.Error()),
|
|
|
|
}
|
2021-06-16 17:55:41 +00:00
|
|
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
2018-06-15 14:13:11 +00:00
|
|
|
enc.Encode(ve)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-01-11 02:00:46 +00:00
|
|
|
type mailError interface {
|
|
|
|
MailError() []map[string]string
|
|
|
|
}
|
|
|
|
if e, ok := err.(mailError); ok {
|
|
|
|
me := jsonError{
|
|
|
|
Message: "Mail Error",
|
|
|
|
Errors: e.MailError(),
|
|
|
|
}
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
enc.Encode(me)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-09-29 04:21:39 +00:00
|
|
|
type osqueryError interface {
|
|
|
|
error
|
|
|
|
NodeInvalid() bool
|
|
|
|
}
|
|
|
|
if e, ok := err.(osqueryError); ok {
|
|
|
|
// osquery expects to receive the node_invalid key when a TLS
|
|
|
|
// request provides an invalid node_key for authentication. It
|
|
|
|
// doesn't use the error message provided, but we provide this
|
|
|
|
// for debugging purposes (and perhaps osquery will use this
|
|
|
|
// error message in the future).
|
|
|
|
|
|
|
|
errMap := map[string]interface{}{"error": e.Error()}
|
|
|
|
if e.NodeInvalid() {
|
|
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
|
|
errMap["node_invalid"] = true
|
|
|
|
} else {
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
|
|
|
enc.Encode(errMap)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-12-20 18:35:22 +00:00
|
|
|
type notFoundError interface {
|
|
|
|
error
|
|
|
|
IsNotFound() bool
|
|
|
|
}
|
|
|
|
if e, ok := err.(notFoundError); ok {
|
|
|
|
je := jsonError{
|
|
|
|
Message: "Resource Not Found",
|
2016-12-30 00:40:12 +00:00
|
|
|
Errors: baseError(e.Error()),
|
2016-12-20 18:35:22 +00:00
|
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNotFound)
|
|
|
|
enc.Encode(je)
|
2016-12-30 00:40:12 +00:00
|
|
|
return
|
2016-12-20 18:35:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type existsError interface {
|
|
|
|
error
|
|
|
|
IsExists() bool
|
|
|
|
}
|
|
|
|
if e, ok := err.(existsError); ok {
|
|
|
|
je := jsonError{
|
|
|
|
Message: "Resource Already Exists",
|
2016-12-30 00:40:12 +00:00
|
|
|
Errors: baseError(e.Error()),
|
2016-12-20 18:35:22 +00:00
|
|
|
}
|
|
|
|
w.WriteHeader(http.StatusConflict)
|
|
|
|
enc.Encode(je)
|
2016-12-30 00:40:12 +00:00
|
|
|
return
|
2016-12-20 18:35:22 +00:00
|
|
|
}
|
|
|
|
|
2021-03-25 02:36:30 +00:00
|
|
|
// Get specific status code if it is available from this error type,
|
|
|
|
// defaulting to HTTP 500
|
|
|
|
status := http.StatusInternalServerError
|
2021-06-16 17:55:41 +00:00
|
|
|
if e, ok := err.(kithttp.StatusCoder); ok {
|
2021-03-25 02:36:30 +00:00
|
|
|
status = e.StatusCode()
|
|
|
|
}
|
|
|
|
|
2021-03-26 18:23:29 +00:00
|
|
|
// See header documentation
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After)
|
2021-06-06 22:07:29 +00:00
|
|
|
if e, ok := err.(fleet.ErrWithRetryAfter); ok {
|
2021-03-26 18:23:29 +00:00
|
|
|
w.Header().Add("Retry-After", strconv.Itoa(e.RetryAfter()))
|
|
|
|
}
|
|
|
|
|
2021-03-25 02:36:30 +00:00
|
|
|
w.WriteHeader(status)
|
2016-12-30 00:40:12 +00:00
|
|
|
je := jsonError{
|
2021-03-25 02:36:30 +00:00
|
|
|
Message: err.Error(),
|
2016-12-30 00:40:12 +00:00
|
|
|
Errors: baseError(err.Error()),
|
|
|
|
}
|
|
|
|
enc.Encode(je)
|
2016-09-23 02:41:58 +00:00
|
|
|
}
|