mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Improve error handling throughout backend (#50)
* New function `errors.ReturnError` for writing errors into the HTTP response * New type `KolideError` that includes additional error context * Validation and application errors are reported in a consistent JSON format * Add 404 handler * Refactored error handling throughout codebase to use new error patterns
This commit is contained in:
parent
2c15647b6e
commit
604e3e4fb0
12 changed files with 787 additions and 429 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
|||
kolide.exe
|
||||
kolide
|
||||
kolide-ose*
|
||||
vendor/
|
||||
vendor/
|
||||
*.test
|
||||
14
Godeps/Godeps.json
generated
14
Godeps/Godeps.json
generated
|
|
@ -20,6 +20,10 @@
|
|||
"ImportPath": "github.com/alecthomas/units",
|
||||
"Rev": "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||
"Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/dgrijalva/jwt-go",
|
||||
"Comment": "v3.0.0-4-g01aeca5",
|
||||
|
|
@ -81,6 +85,16 @@
|
|||
"Comment": "v1.1.0-71-ge118d44",
|
||||
"Rev": "e118d4451349065b8e7ce0f0af32e033995363f8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
||||
"Comment": "v1.0.0",
|
||||
"Rev": "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/stretchr/testify/assert",
|
||||
"Comment": "v1.1.3-19-gd77da35",
|
||||
"Rev": "d77da356e56a7428ad25149ca77381849a6a5232"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/bcrypt",
|
||||
"Rev": "bc89c496413265e715159bdc8478ee9a92fdc265"
|
||||
|
|
|
|||
18
app/auth.go
18
app/auth.go
|
|
@ -3,13 +3,13 @@ package app
|
|||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/kolide/kolide-ose/config"
|
||||
"github.com/kolide/kolide-ose/errors"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ func (vc *ViewerContext) UserID() (uint, error) {
|
|||
if vc.user != nil {
|
||||
return vc.user.ID, nil
|
||||
}
|
||||
return 0, errors.New("No user set")
|
||||
return 0, errors.New("Unauthorized", "No user set")
|
||||
}
|
||||
|
||||
// CanPerformActions returns a bool indicating the current user's ability to
|
||||
|
|
@ -157,8 +157,8 @@ func SaltAndHashPassword(password string) (string, []byte, error) {
|
|||
|
||||
// swagger:parameters Login
|
||||
type LoginRequestBody struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
}
|
||||
|
||||
// swagger:route POST /api/v1/kolide/login Login
|
||||
|
|
@ -185,9 +185,9 @@ type LoginRequestBody struct {
|
|||
// 200: GetUserResponseBody
|
||||
func Login(c *gin.Context) {
|
||||
var body LoginRequestBody
|
||||
err := c.BindJSON(&body)
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing Login post body: %s", err.Error())
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +212,7 @@ func Login(c *gin.Context) {
|
|||
sm.MakeSessionForUserID(user.ID)
|
||||
err = sm.Save()
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -252,13 +252,13 @@ func Logout(c *gin.Context) {
|
|||
|
||||
err := sm.Destroy()
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
err = sm.Save()
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,24 +9,23 @@ import (
|
|||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
"github.com/kolide/kolide-ose/config"
|
||||
"github.com/kolide/kolide-ose/osquery"
|
||||
"github.com/kolide/kolide-ose/sessions"
|
||||
)
|
||||
|
||||
var tables = [...]interface{}{
|
||||
&User{},
|
||||
&sessions.Session{},
|
||||
&osquery.ScheduledQuery{},
|
||||
&osquery.Pack{},
|
||||
&osquery.DiscoveryQuery{},
|
||||
&osquery.Host{},
|
||||
&osquery.Label{},
|
||||
&osquery.Option{},
|
||||
&osquery.Decorator{},
|
||||
&osquery.Target{},
|
||||
&osquery.DistributedQuery{},
|
||||
&osquery.Query{},
|
||||
&osquery.DistributedQueryExecution{},
|
||||
&ScheduledQuery{},
|
||||
&Pack{},
|
||||
&DiscoveryQuery{},
|
||||
&Host{},
|
||||
&Label{},
|
||||
&Option{},
|
||||
&Decorator{},
|
||||
&Target{},
|
||||
&DistributedQuery{},
|
||||
&Query{},
|
||||
&DistributedQueryExecution{},
|
||||
}
|
||||
|
||||
func setDBSettings(db *gorm.DB) {
|
||||
|
|
|
|||
324
app/osquery.go
Normal file
324
app/osquery.go
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/kolide/kolide-ose/errors"
|
||||
)
|
||||
|
||||
type ScheduledQuery struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string `gorm:"not null"`
|
||||
QueryID int
|
||||
Query Query
|
||||
Interval uint `gorm:"not null"`
|
||||
Snapshot bool
|
||||
Differential bool
|
||||
Platform string
|
||||
PackID uint
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Query string `gorm:"not null"`
|
||||
Targets []Target `gorm:"many2many:query_targets"`
|
||||
}
|
||||
|
||||
type TargetType int
|
||||
|
||||
const (
|
||||
TargetLabel TargetType = iota
|
||||
TargetHost TargetType = iota
|
||||
)
|
||||
|
||||
type Target struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Type TargetType
|
||||
QueryID uint
|
||||
TargetID uint
|
||||
}
|
||||
|
||||
type DistributedQueryStatus int
|
||||
|
||||
const (
|
||||
QueryRunning DistributedQueryStatus = iota
|
||||
QueryComplete DistributedQueryStatus = iota
|
||||
QueryError DistributedQueryStatus = iota
|
||||
)
|
||||
|
||||
type DistributedQuery struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Query Query
|
||||
MaxDuration time.Duration
|
||||
Status DistributedQueryStatus
|
||||
UserID uint
|
||||
}
|
||||
|
||||
type DistributedQueryExecutionStatus int
|
||||
|
||||
const (
|
||||
ExecutionWaiting DistributedQueryExecutionStatus = iota
|
||||
ExecutionRequested DistributedQueryExecutionStatus = iota
|
||||
ExecutionSucceeded DistributedQueryExecutionStatus = iota
|
||||
ExecutionFailed DistributedQueryExecutionStatus = iota
|
||||
)
|
||||
|
||||
type DistributedQueryExecution struct {
|
||||
HostID uint
|
||||
DistributedQueryID uint
|
||||
Status DistributedQueryExecutionStatus
|
||||
Error string `gorm:"size:1024"`
|
||||
ExecutionDuration time.Duration
|
||||
}
|
||||
|
||||
type Pack struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string `gorm:"not null;unique_index:idx_pack_unique_name"`
|
||||
Platform string
|
||||
Queries []ScheduledQuery
|
||||
DiscoveryQueries []DiscoveryQuery
|
||||
}
|
||||
|
||||
type DiscoveryQuery struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Query string `gorm:"size:1024" gorm:"not null"`
|
||||
}
|
||||
|
||||
type Host struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
NodeKey string `gorm:"unique_index:idx_host_unique_nodekey"`
|
||||
HostName string
|
||||
UUID string `gorm:"unique_index:idx_host_unique_uuid"`
|
||||
IPAddress string
|
||||
Platform string
|
||||
Labels []*Label `gorm:"many2many:host_labels;"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string `gorm:"not null;unique_index:idx_label_unique_name"`
|
||||
Query string
|
||||
Hosts []Host
|
||||
}
|
||||
|
||||
type Option struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Key string `gorm:"not null;unique_index:idx_option_unique_key"`
|
||||
Value string `gorm:"not null"`
|
||||
Platform string
|
||||
}
|
||||
|
||||
type DecoratorType int
|
||||
|
||||
const (
|
||||
DecoratorLoad DecoratorType = iota
|
||||
DecoratorAlways DecoratorType = iota
|
||||
DecoratorInterval DecoratorType = iota
|
||||
)
|
||||
|
||||
type Decorator struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Type DecoratorType `gorm:"not null"`
|
||||
Interval int
|
||||
Query string
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type OsqueryEnrollPostBody struct {
|
||||
EnrollSecret string `json:"enroll_secret" validate:"required"`
|
||||
}
|
||||
|
||||
type OsqueryConfigPostBody struct {
|
||||
NodeKey string `json:"node_key" validate:"required"`
|
||||
}
|
||||
|
||||
type OsqueryLogPostBody struct {
|
||||
NodeKey string `json:"node_key" validate:"required"`
|
||||
LogType string `json:"log_type" validate:"required"`
|
||||
Data []map[string]interface{} `json:"data" validate:"required"`
|
||||
}
|
||||
|
||||
type OsqueryResultLog struct {
|
||||
Name string `json:"name"`
|
||||
HostIdentifier string `json:"hostIdentifier"`
|
||||
UnixTime string `json:"unixTime"`
|
||||
CalendarTime string `json:"calendarTime"`
|
||||
Columns map[string]string `json:"columns"`
|
||||
Action string `json:"action"`
|
||||
}
|
||||
|
||||
type OsqueryStatusLog struct {
|
||||
Severity string `json:"severity"`
|
||||
Filename string `json:"filename"`
|
||||
Line string `json:"line"`
|
||||
Message string `json:"message"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type OsqueryDistributedReadPostBody struct {
|
||||
NodeKey string `json:"node_key" validate:"required"`
|
||||
}
|
||||
|
||||
type OsqueryDistributedWritePostBody struct {
|
||||
NodeKey string `json:"node_key" validate:"required"`
|
||||
Queries map[string][]map[string]string `json:"queries" validate:"required"`
|
||||
}
|
||||
|
||||
func OsqueryEnroll(c *gin.Context) {
|
||||
var body OsqueryEnrollPostBody
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
logrus.Debugf("OsqueryEnroll: %s", body.EnrollSecret)
|
||||
|
||||
c.JSON(http.StatusOK,
|
||||
gin.H{
|
||||
"node_key": "7",
|
||||
"node_invalid": false,
|
||||
})
|
||||
}
|
||||
|
||||
func OsqueryConfig(c *gin.Context) {
|
||||
var body OsqueryConfigPostBody
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
logrus.Debugf("OsqueryConfig: %s", body.NodeKey)
|
||||
|
||||
c.JSON(http.StatusOK,
|
||||
gin.H{
|
||||
"schedule": map[string]map[string]interface{}{
|
||||
"time": {
|
||||
"query": "select * from time;",
|
||||
"interval": 1,
|
||||
},
|
||||
},
|
||||
"node_invalid": false,
|
||||
})
|
||||
}
|
||||
|
||||
func OsqueryLog(c *gin.Context) {
|
||||
var body OsqueryLogPostBody
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
logrus.Debugf("OsqueryLog: %s", body.LogType)
|
||||
|
||||
if body.LogType == "status" {
|
||||
for _, data := range body.Data {
|
||||
var log OsqueryStatusLog
|
||||
|
||||
severity, ok := data["severity"].(string)
|
||||
if ok {
|
||||
log.Severity = severity
|
||||
} else {
|
||||
logrus.Error("Error asserting the type of status log severity")
|
||||
}
|
||||
|
||||
filename, ok := data["filename"].(string)
|
||||
if ok {
|
||||
log.Filename = filename
|
||||
} else {
|
||||
logrus.Error("Error asserting the type of status log filename")
|
||||
}
|
||||
|
||||
line, ok := data["line"].(string)
|
||||
if ok {
|
||||
log.Line = line
|
||||
} else {
|
||||
logrus.Error("Error asserting the type of status log line")
|
||||
}
|
||||
|
||||
message, ok := data["message"].(string)
|
||||
if ok {
|
||||
log.Message = message
|
||||
} else {
|
||||
logrus.Error("Error asserting the type of status log message")
|
||||
}
|
||||
|
||||
version, ok := data["version"].(string)
|
||||
if ok {
|
||||
log.Version = version
|
||||
} else {
|
||||
logrus.Error("Error asserting the type of status log version")
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"node_key": body.NodeKey,
|
||||
"severity": log.Severity,
|
||||
"filename": log.Filename,
|
||||
"line": log.Line,
|
||||
"version": log.Version,
|
||||
}).Info(log.Message)
|
||||
}
|
||||
} else if body.LogType == "result" {
|
||||
// TODO: handle all of the different kinds of results logs
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK,
|
||||
gin.H{
|
||||
"node_invalid": false,
|
||||
})
|
||||
}
|
||||
|
||||
func OsqueryDistributedRead(c *gin.Context) {
|
||||
var body OsqueryDistributedReadPostBody
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
logrus.Debugf("OsqueryDistributedRead: %s", body.NodeKey)
|
||||
|
||||
c.JSON(http.StatusOK,
|
||||
gin.H{
|
||||
"queries": map[string]string{
|
||||
"id1": "select * from osquery_info",
|
||||
},
|
||||
"node_invalid": false,
|
||||
})
|
||||
}
|
||||
|
||||
func OsqueryDistributedWrite(c *gin.Context) {
|
||||
var body OsqueryDistributedWritePostBody
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
logrus.Debugf("OsqueryDistributedWrite: %s", body.NodeKey)
|
||||
c.JSON(http.StatusOK,
|
||||
gin.H{
|
||||
"node_invalid": false,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"time"
|
||||
|
||||
|
|
@ -10,43 +13,32 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/kolide/kolide-ose/config"
|
||||
"github.com/kolide/kolide-ose/osquery"
|
||||
"github.com/kolide/kolide-ose/errors"
|
||||
"github.com/kolide/kolide-ose/sessions"
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
var validate *validator.Validate = validator.New(&validator.Config{TagName: "validate", FieldNameTag: "json"})
|
||||
|
||||
// Get the database connection from the context, or panic
|
||||
func GetDB(c *gin.Context) *gorm.DB {
|
||||
return c.MustGet("DB").(*gorm.DB)
|
||||
}
|
||||
|
||||
// ServerError is a helper which accepts a string error and returns a map in
|
||||
// format that is required by gin.Context.JSON
|
||||
func ServerError(e string) *map[string]interface{} {
|
||||
return &map[string]interface{}{
|
||||
"error": e,
|
||||
}
|
||||
}
|
||||
|
||||
// DatabaseError emits a response that is appropriate in the event that a
|
||||
// database failure occurs, a record is not found in the database, etc
|
||||
func DatabaseError(c *gin.Context) {
|
||||
c.JSON(500, ServerError("Database error"))
|
||||
}
|
||||
|
||||
// UnauthorizedError emits a response that is appropriate in the event that a
|
||||
// request is received by a user which is not authorized to carry out the
|
||||
// requested action
|
||||
func UnauthorizedError(c *gin.Context) {
|
||||
c.JSON(401, ServerError("Unauthorized"))
|
||||
}
|
||||
|
||||
// MalformedRequestError emits a response that is appropriate in the event that
|
||||
// a request is received by a user which does not have required fields or is in
|
||||
// some way malformed
|
||||
func MalformedRequestError(c *gin.Context) {
|
||||
c.JSON(400, ServerError("Malformed request"))
|
||||
errors.ReturnError(
|
||||
c,
|
||||
errors.NewWithStatus(
|
||||
http.StatusUnauthorized,
|
||||
"Unauthorized",
|
||||
"Unauthorized",
|
||||
))
|
||||
}
|
||||
|
||||
// Create a new server for testing purposes with no routes attached
|
||||
func createEmptyTestServer(db *gorm.DB) *gin.Engine {
|
||||
server := gin.New()
|
||||
server.Use(DatabaseMiddleware(db))
|
||||
|
|
@ -73,6 +65,35 @@ func NewSessionManager(c *gin.Context) *sessions.SessionManager {
|
|||
}
|
||||
}
|
||||
|
||||
// Unmarshal JSON from the gin context into a struct
|
||||
func parseJSON(c *gin.Context, obj interface{}) error {
|
||||
decoder := json.NewDecoder(c.Request.Body)
|
||||
if err := decoder.Decode(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse JSON into a struct with json.Unmarshal, followed by validation with
|
||||
// the validator library.
|
||||
func ParseAndValidateJSON(c *gin.Context, obj interface{}) error {
|
||||
if err := parseJSON(c, obj); err != nil {
|
||||
return errors.NewFromError(err, http.StatusBadRequest, "JSON parse error")
|
||||
}
|
||||
|
||||
return validate.Struct(obj)
|
||||
}
|
||||
|
||||
func NotFound(c *gin.Context) {
|
||||
errors.ReturnError(
|
||||
c,
|
||||
errors.NewWithStatus(
|
||||
http.StatusNotFound,
|
||||
"Not found",
|
||||
fmt.Sprintf("Route not found for request: %+v", c.Request),
|
||||
))
|
||||
}
|
||||
|
||||
// CreateServer creates a gin.Engine HTTP server and configures it to be in a
|
||||
// state such that it is ready to serve HTTP requests for the kolide application
|
||||
func CreateServer(db *gorm.DB, w io.Writer) *gin.Engine {
|
||||
|
|
@ -105,6 +126,9 @@ func CreateServer(db *gorm.DB, w io.Writer) *gin.Engine {
|
|||
recoveryLogger.Out = w
|
||||
server.Use(gin.RecoveryWithWriter(recoveryLogger.Writer()))
|
||||
|
||||
// Set the 404 route
|
||||
server.NoRoute(NotFound)
|
||||
|
||||
v1 := server.Group("/api/v1")
|
||||
|
||||
// Kolide application API endpoints
|
||||
|
|
@ -130,11 +154,11 @@ func CreateServer(db *gorm.DB, w io.Writer) *gin.Engine {
|
|||
|
||||
// osquery API endpoints
|
||||
osq := v1.Group("/osquery")
|
||||
osq.POST("/enroll", osquery.OsqueryEnroll)
|
||||
osq.POST("/config", osquery.OsqueryConfig)
|
||||
osq.POST("/log", osquery.OsqueryLog)
|
||||
osq.POST("/distributed/read", osquery.OsqueryDistributedRead)
|
||||
osq.POST("/distributed/write", osquery.OsqueryDistributedWrite)
|
||||
osq.POST("/enroll", OsqueryEnroll)
|
||||
osq.POST("/config", OsqueryConfig)
|
||||
osq.POST("/log", OsqueryLog)
|
||||
osq.POST("/distributed/read", OsqueryDistributedRead)
|
||||
osq.POST("/distributed/write", OsqueryDistributedWrite)
|
||||
|
||||
return server
|
||||
}
|
||||
|
|
|
|||
102
app/users.go
102
app/users.go
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/kolide/kolide-ose/errors"
|
||||
"github.com/kolide/kolide-ose/sessions"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
|
@ -127,9 +128,9 @@ type GetUserResponseBody struct {
|
|||
// 200: GetUserResponseBody
|
||||
func GetUser(c *gin.Context) {
|
||||
var body GetUserRequestBody
|
||||
err := c.BindJSON(&body)
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing GetUser post body: %s", err.Error())
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -145,7 +146,7 @@ func GetUser(c *gin.Context) {
|
|||
user.Username = body.Username
|
||||
err = db.Where(&user).First(&user).Error
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -167,9 +168,9 @@ func GetUser(c *gin.Context) {
|
|||
|
||||
// swagger:parameters CreateUser
|
||||
type CreateUserRequestBody struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
Email string `json:"email" binding:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Admin bool `json:"admin"`
|
||||
NeedsPasswordReset bool `json:"needs_password_reset"`
|
||||
}
|
||||
|
|
@ -196,9 +197,9 @@ type CreateUserRequestBody struct {
|
|||
// 200: GetUserResponseBody
|
||||
func CreateUser(c *gin.Context) {
|
||||
var body CreateUserRequestBody
|
||||
err := c.BindJSON(&body)
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing CreateUser post body: %s", err.Error())
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +213,7 @@ func CreateUser(c *gin.Context) {
|
|||
user, err := NewUser(db, body.Username, body.Password, body.Email, body.Admin, body.NeedsPasswordReset)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error creating new user: %s", err.Error())
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -259,9 +260,9 @@ type ModifyUserRequestBody struct {
|
|||
// 200: GetUserResponseBody
|
||||
func ModifyUser(c *gin.Context) {
|
||||
var body ModifyUserRequestBody
|
||||
err := c.BindJSON(&body)
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing ModifyUser post body: %s", err.Error())
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -278,7 +279,7 @@ func ModifyUser(c *gin.Context) {
|
|||
db := GetDB(c)
|
||||
err = db.Where(&user).First(&user).Error
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -296,7 +297,7 @@ func ModifyUser(c *gin.Context) {
|
|||
err = db.Save(&user).Error
|
||||
if err != nil {
|
||||
logrus.Errorf("Error updating user in database: %s", err.Error())
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
c.JSON(200, GetUserResponseBody{
|
||||
|
|
@ -338,9 +339,9 @@ type DeleteUserRequestBody struct {
|
|||
// 200: nil
|
||||
func DeleteUser(c *gin.Context) {
|
||||
var body DeleteUserRequestBody
|
||||
err := c.BindJSON(&body)
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing DeleteUser post body: %s", err.Error())
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -356,14 +357,14 @@ func DeleteUser(c *gin.Context) {
|
|||
user.Username = body.Username
|
||||
err = db.Where(&user).First(&user).Error
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Delete(&user).Error
|
||||
if err != nil {
|
||||
logrus.Errorf("Error deleting user from database: %s", err.Error())
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
c.JSON(200, nil)
|
||||
|
|
@ -374,8 +375,8 @@ type ChangePasswordRequestBody struct {
|
|||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
CurrentPassword string `json:"current_password"`
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
NewPasswordConfim string `json:"new_password_confirm" binding:"required"`
|
||||
NewPassword string `json:"new_password" validate:"required"`
|
||||
NewPasswordConfim string `json:"new_password_confirm" validate:"required"`
|
||||
}
|
||||
|
||||
// swagger:route PATCH /api/v1/kolide/user/password ChangeUserPassword
|
||||
|
|
@ -402,9 +403,9 @@ type ChangePasswordRequestBody struct {
|
|||
// 200: GetUserResponseBody
|
||||
func ChangeUserPassword(c *gin.Context) {
|
||||
var body ChangePasswordRequestBody
|
||||
err := c.BindJSON(&body)
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing ResetPassword post body: %s", err.Error())
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -425,7 +426,7 @@ func ChangeUserPassword(c *gin.Context) {
|
|||
user.Username = body.Username
|
||||
err = db.Where(&user).First(&user).Error
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -443,14 +444,14 @@ func ChangeUserPassword(c *gin.Context) {
|
|||
err = user.SetPassword(db, body.NewPassword)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error setting user password: %s", err.Error())
|
||||
DatabaseError(c) // probably not this
|
||||
errors.ReturnError(c, errors.DatabaseError(err)) // probably not this
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Save(&user).Error
|
||||
if err != nil {
|
||||
logrus.Errorf("Error updating user in database: %s", err.Error())
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
c.JSON(200, GetUserResponseBody{
|
||||
|
|
@ -493,9 +494,9 @@ type SetUserAdminStateRequestBody struct {
|
|||
// 200: GetUserResponseBody
|
||||
func SetUserAdminState(c *gin.Context) {
|
||||
var body SetUserAdminStateRequestBody
|
||||
err := c.BindJSON(&body)
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing SetUserAdminState post body: %s", err.Error())
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -511,7 +512,7 @@ func SetUserAdminState(c *gin.Context) {
|
|||
user.Username = body.Username
|
||||
err = db.Where(&user).First(&user).Error
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -519,7 +520,7 @@ func SetUserAdminState(c *gin.Context) {
|
|||
err = db.Save(&user).Error
|
||||
if err != nil {
|
||||
logrus.Errorf("Error updating user in database: %s", err.Error())
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
c.JSON(200, GetUserResponseBody{
|
||||
|
|
@ -562,9 +563,9 @@ type SetUserEnabledStateRequestBody struct {
|
|||
// 200: GetUserResponseBody
|
||||
func SetUserEnabledState(c *gin.Context) {
|
||||
var body SetUserEnabledStateRequestBody
|
||||
err := c.BindJSON(&body)
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing SetUserEnabledState post body: %s", err.Error())
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -580,7 +581,7 @@ func SetUserEnabledState(c *gin.Context) {
|
|||
user.Username = body.Username
|
||||
err = db.Where(&user).First(&user).Error
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -588,7 +589,7 @@ func SetUserEnabledState(c *gin.Context) {
|
|||
err = db.Save(&user).Error
|
||||
if err != nil {
|
||||
logrus.Errorf("Error updating user in database: %s", err.Error())
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
c.JSON(200, GetUserResponseBody{
|
||||
|
|
@ -624,7 +625,7 @@ func GetSessionBackend(c *gin.Context) sessions.SessionBackend {
|
|||
|
||||
// swagger:parameters DeleteSession
|
||||
type DeleteSessionRequestBody struct {
|
||||
SessionID uint `json:"session_id" binding:"required"`
|
||||
SessionID uint `json:"session_id" validate:"required"`
|
||||
}
|
||||
|
||||
// swagger:route DELETE /api/v1/kolide/session DeleteSession
|
||||
|
|
@ -649,9 +650,9 @@ type DeleteSessionRequestBody struct {
|
|||
// 200: nil
|
||||
func DeleteSession(c *gin.Context) {
|
||||
var body DeleteSessionRequestBody
|
||||
err := c.BindJSON(&body)
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
logrus.Errorf(err.Error())
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -672,7 +673,7 @@ func DeleteSession(c *gin.Context) {
|
|||
user := &User{ID: session.UserID}
|
||||
err = db.Where(user).First(user).Error
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -683,7 +684,7 @@ func DeleteSession(c *gin.Context) {
|
|||
|
||||
err = sb.Destroy(session)
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -719,9 +720,10 @@ type DeleteSessionsForUserRequestBody struct {
|
|||
// 200: nil
|
||||
func DeleteSessionsForUser(c *gin.Context) {
|
||||
var body DeleteSessionsForUserRequestBody
|
||||
err := c.BindJSON(&body)
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
logrus.Errorf(err.Error())
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
vc := VC(c)
|
||||
|
|
@ -736,7 +738,7 @@ func DeleteSessionsForUser(c *gin.Context) {
|
|||
user.Username = body.Username
|
||||
err = db.Where(&user).First(&user).Error
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -748,7 +750,7 @@ func DeleteSessionsForUser(c *gin.Context) {
|
|||
sb := GetSessionBackend(c)
|
||||
err = sb.DestroyAllForUser(user.ID)
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -758,7 +760,7 @@ func DeleteSessionsForUser(c *gin.Context) {
|
|||
|
||||
// swagger:parameters GetInfoAboutSession
|
||||
type GetInfoAboutSessionRequestBody struct {
|
||||
SessionKey string `json:"session_key" binding:"required"`
|
||||
SessionKey string `json:"session_key" validate:"required"`
|
||||
}
|
||||
|
||||
// swagger:response SessionInfoResponseBody
|
||||
|
|
@ -791,9 +793,9 @@ type SessionInfoResponseBody struct {
|
|||
// 200: SessionInfoResponseBody
|
||||
func GetInfoAboutSession(c *gin.Context) {
|
||||
var body GetInfoAboutSessionRequestBody
|
||||
err := c.BindJSON(&body)
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
logrus.Errorf(err.Error())
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -806,7 +808,7 @@ func GetInfoAboutSession(c *gin.Context) {
|
|||
sb := GetSessionBackend(c)
|
||||
session, err := sb.FindKey(body.SessionKey)
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -815,7 +817,7 @@ func GetInfoAboutSession(c *gin.Context) {
|
|||
user.ID = session.UserID
|
||||
err = db.Where(&user).First(&user).Error
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -865,9 +867,9 @@ type GetInfoAboutSessionsForUserResponseBody struct {
|
|||
// 200: GetInfoAboutSessionsForUserResponseBody
|
||||
func GetInfoAboutSessionsForUser(c *gin.Context) {
|
||||
var body GetInfoAboutSessionsForUserRequestBody
|
||||
err := c.BindJSON(&body)
|
||||
err := ParseAndValidateJSON(c, &body)
|
||||
if err != nil {
|
||||
logrus.Errorf(err.Error())
|
||||
errors.ReturnError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -883,7 +885,7 @@ func GetInfoAboutSessionsForUser(c *gin.Context) {
|
|||
user.Username = body.Username
|
||||
err = db.Where(&user).First(&user).Error
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -895,7 +897,7 @@ func GetInfoAboutSessionsForUser(c *gin.Context) {
|
|||
sb := GetSessionBackend(c)
|
||||
sessions, err := sb.FindAllForUser(user.ID)
|
||||
if err != nil {
|
||||
DatabaseError(c)
|
||||
errors.ReturnError(c, errors.DatabaseError(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
104
errors/errors.go
Normal file
104
errors/errors.go
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/gorm"
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
// Kolide's internal representation for errors. It can be used to wrap another
|
||||
// error (stored in Err), and additionally contains fields for public
|
||||
// (PublicMessage) and private (PrivateMessage) error messages as well as the
|
||||
// HTTP status code (StatusCode) corresponding to the error.
|
||||
type KolideError struct {
|
||||
Err error
|
||||
StatusCode int
|
||||
PublicMessage string
|
||||
PrivateMessage string
|
||||
}
|
||||
|
||||
// Implementation of error interface
|
||||
func (e *KolideError) Error() string {
|
||||
return e.PublicMessage
|
||||
}
|
||||
|
||||
// Create a new KolideError specifying the public and private messages. The
|
||||
// status code will be set to 500.
|
||||
func New(publicMessage, privateMessage string) *KolideError {
|
||||
return &KolideError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
PublicMessage: publicMessage,
|
||||
PrivateMessage: privateMessage,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new KolideError specifying the HTTP status, and public and private
|
||||
// messages.
|
||||
func NewWithStatus(status int, publicMessage, privateMessage string) *KolideError {
|
||||
return &KolideError{
|
||||
StatusCode: status,
|
||||
PublicMessage: publicMessage,
|
||||
PrivateMessage: privateMessage,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new KolideError from an error type. The public message and status
|
||||
// code should be specified, while the private message will be drawn from
|
||||
// err.Error()
|
||||
func NewFromError(err error, status int, publicMessage string) *KolideError {
|
||||
return &KolideError{
|
||||
Err: err,
|
||||
StatusCode: status,
|
||||
PublicMessage: publicMessage,
|
||||
PrivateMessage: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap a DB error wit the extra KolideError decorations
|
||||
func DatabaseError(err error) *KolideError {
|
||||
return NewFromError(err, http.StatusInternalServerError, "Database error")
|
||||
}
|
||||
|
||||
// The status code returned for validation errors. Inspired by the Github API.
|
||||
const StatusUnprocessableEntity = 422
|
||||
|
||||
// Handle an error, printing debug information, writing to the HTTP response as
|
||||
// appropriate for the dynamic error type.
|
||||
func ReturnError(c *gin.Context, err error) {
|
||||
switch typedErr := err.(type) {
|
||||
|
||||
case *KolideError:
|
||||
c.JSON(typedErr.StatusCode,
|
||||
gin.H{"message": typedErr.PublicMessage})
|
||||
logrus.WithError(typedErr.Err).Debug(typedErr.PrivateMessage)
|
||||
|
||||
case validator.ValidationErrors:
|
||||
errors := make([](map[string]string), 0, len(typedErr))
|
||||
for _, fieldErr := range typedErr {
|
||||
m := make(map[string]string)
|
||||
m["field"] = fieldErr.Name
|
||||
m["code"] = "invalid"
|
||||
m["message"] = fieldErr.Tag
|
||||
errors = append(errors, m)
|
||||
}
|
||||
|
||||
c.JSON(StatusUnprocessableEntity,
|
||||
gin.H{"message": "Validation error",
|
||||
"errors": errors,
|
||||
})
|
||||
logrus.WithError(typedErr).Debug("Validation error")
|
||||
|
||||
case gorm.Errors, *gorm.Errors:
|
||||
c.JSON(http.StatusInternalServerError,
|
||||
gin.H{"message": "Database error"})
|
||||
logrus.WithError(typedErr).Debug(typedErr.Error())
|
||||
|
||||
default:
|
||||
c.JSON(http.StatusInternalServerError,
|
||||
gin.H{"message": "Unspecified error"})
|
||||
logrus.WithError(typedErr).Debug("Unspecified error")
|
||||
}
|
||||
}
|
||||
211
errors/errors_test.go
Normal file
211
errors/errors_test.go
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
kolideErr := New("Public message", "Private message")
|
||||
|
||||
expect := &KolideError{
|
||||
Err: nil,
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
PublicMessage: "Public message",
|
||||
PrivateMessage: "Private message",
|
||||
}
|
||||
assert.Equal(t, expect, kolideErr)
|
||||
}
|
||||
|
||||
func TestNewWithStatus(t *testing.T) {
|
||||
kolideErr := NewWithStatus(http.StatusUnauthorized, "Public message", "Private message")
|
||||
|
||||
expect := &KolideError{
|
||||
Err: nil,
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
PublicMessage: "Public message",
|
||||
PrivateMessage: "Private message",
|
||||
}
|
||||
assert.Equal(t, expect, kolideErr)
|
||||
}
|
||||
|
||||
func TestNewFromError(t *testing.T) {
|
||||
err := errors.New("Foo error")
|
||||
kolideErr := NewFromError(err, StatusUnprocessableEntity, "Public error")
|
||||
|
||||
assert.Equal(t, "Public error", kolideErr.Error())
|
||||
|
||||
expect := &KolideError{
|
||||
Err: err,
|
||||
StatusCode: StatusUnprocessableEntity,
|
||||
PublicMessage: "Public error",
|
||||
PrivateMessage: "Foo error",
|
||||
}
|
||||
assert.Equal(t, expect, kolideErr)
|
||||
}
|
||||
|
||||
func TestDatabaseError(t *testing.T) {
|
||||
err := errors.New("Foo error")
|
||||
kolideErr := DatabaseError(err)
|
||||
|
||||
expect := &KolideError{
|
||||
Err: err,
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
PublicMessage: "Database error",
|
||||
PrivateMessage: "Foo error",
|
||||
}
|
||||
assert.Equal(t, expect, kolideErr)
|
||||
}
|
||||
|
||||
func TestReturnErrorUnspecified(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.POST("/foo", func(c *gin.Context) {
|
||||
ReturnError(c, errors.New("foo"))
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest("POST", "/foo", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(resp, req)
|
||||
|
||||
if resp.Code != http.StatusInternalServerError {
|
||||
t.Errorf("Should respond with 500, got %d", resp.Code)
|
||||
}
|
||||
|
||||
expect := `{"message": "Unspecified error"}`
|
||||
assert.JSONEq(t, expect, resp.Body.String())
|
||||
}
|
||||
|
||||
func TestReturnErrorKolideError(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.POST("/foo", func(c *gin.Context) {
|
||||
ReturnError(c, &KolideError{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
PublicMessage: "Some error",
|
||||
})
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest("POST", "/foo", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(resp, req)
|
||||
|
||||
if resp.Code != http.StatusUnauthorized {
|
||||
t.Errorf("Should respond with 403, got %d", resp.Code)
|
||||
}
|
||||
|
||||
expect := `{"message": "Some error"}`
|
||||
assert.JSONEq(t, expect, resp.Body.String())
|
||||
}
|
||||
|
||||
// These types and functions for performing an unordered comparison on a
|
||||
// []map[string]string] as parsed from the error JSON
|
||||
type errorField map[string]string
|
||||
type errorFields []errorField
|
||||
|
||||
func (e errorFields) Len() int {
|
||||
return len(e)
|
||||
}
|
||||
|
||||
func (e errorFields) Less(i, j int) bool {
|
||||
return e[i]["field"] <= e[j]["field"] &&
|
||||
e[i]["code"] <= e[j]["code"] &&
|
||||
e[i]["message"] <= e[j]["message"]
|
||||
}
|
||||
|
||||
func (e errorFields) Swap(i, j int) {
|
||||
e[i], e[j] = e[j], e[i]
|
||||
}
|
||||
|
||||
func TestReturnErrorValidationError(t *testing.T) {
|
||||
r := gin.New()
|
||||
|
||||
type Foo struct {
|
||||
Email string `json:"email_foo" validate:"required,email"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
}
|
||||
|
||||
validate := validator.New(&validator.Config{TagName: "validate", FieldNameTag: "json"})
|
||||
|
||||
r.POST("/foo", func(c *gin.Context) {
|
||||
ReturnError(c, validate.Struct(&Foo{Email: "foo", Password: ""}))
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest("POST", "/foo", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(resp, req)
|
||||
|
||||
if resp.Code != StatusUnprocessableEntity {
|
||||
t.Errorf("Should respond with 422, got %d", resp.Code)
|
||||
}
|
||||
|
||||
t.Log(resp.Body.String())
|
||||
|
||||
var bodyJson map[string]interface{}
|
||||
if err := json.Unmarshal(resp.Body.Bytes(), &bodyJson); err != nil {
|
||||
t.Errorf("Error unmarshaling JSON: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, "Validation error", bodyJson["message"])
|
||||
|
||||
fields, ok := bodyJson["errors"].([]interface{})
|
||||
if !ok {
|
||||
t.Errorf("Unexpected type for errors")
|
||||
}
|
||||
|
||||
// The error fields must be copied from []interface{} to
|
||||
// []map[string][string] before we can sort
|
||||
compFields := make(errorFields, 0, 0)
|
||||
for _, field := range fields {
|
||||
field := field.(map[string]interface{})
|
||||
compFields = append(
|
||||
compFields,
|
||||
errorField{
|
||||
"code": field["code"].(string),
|
||||
"field": field["field"].(string),
|
||||
"message": field["message"].(string),
|
||||
})
|
||||
}
|
||||
|
||||
expect := errorFields{
|
||||
{"code": "invalid", "field": "email_foo", "message": "email"},
|
||||
{"code": "invalid", "field": "password", "message": "required"},
|
||||
}
|
||||
|
||||
// Sort to standardize ordering before comparison
|
||||
sort.Sort(compFields)
|
||||
sort.Sort(expect)
|
||||
|
||||
assert.Equal(t, expect, compFields)
|
||||
}
|
||||
|
||||
func TestReturnErrorGormError(t *testing.T) {
|
||||
r := gin.New()
|
||||
|
||||
r.POST("/foo", func(c *gin.Context) {
|
||||
err := gorm.Errors{}
|
||||
err.Add(gorm.ErrInvalidSQL)
|
||||
ReturnError(c, err)
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest("POST", "/foo", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(resp, req)
|
||||
|
||||
if resp.Code != http.StatusInternalServerError {
|
||||
t.Errorf("Should respond with 500, got %d", resp.Code)
|
||||
}
|
||||
|
||||
assert.JSONEq(t, `{"message": "Database error"}`, resp.Body.String())
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
package osquery
|
||||
|
||||
import "time"
|
||||
|
||||
type ScheduledQuery struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string `gorm:"not null"`
|
||||
QueryID int
|
||||
Query Query
|
||||
Interval uint `gorm:"not null"`
|
||||
Snapshot bool
|
||||
Differential bool
|
||||
Platform string
|
||||
PackID uint
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Query string `gorm:"not null"`
|
||||
Targets []Target `gorm:"many2many:query_targets"`
|
||||
}
|
||||
|
||||
type TargetType int
|
||||
|
||||
const (
|
||||
TargetLabel TargetType = iota
|
||||
TargetHost TargetType = iota
|
||||
)
|
||||
|
||||
type Target struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Type TargetType
|
||||
QueryID uint
|
||||
TargetID uint
|
||||
}
|
||||
|
||||
type DistributedQueryStatus int
|
||||
|
||||
const (
|
||||
QueryRunning DistributedQueryStatus = iota
|
||||
QueryComplete DistributedQueryStatus = iota
|
||||
QueryError DistributedQueryStatus = iota
|
||||
)
|
||||
|
||||
type DistributedQuery struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Query Query
|
||||
MaxDuration time.Duration
|
||||
Status DistributedQueryStatus
|
||||
UserID uint
|
||||
}
|
||||
|
||||
type DistributedQueryExecutionStatus int
|
||||
|
||||
const (
|
||||
ExecutionWaiting DistributedQueryExecutionStatus = iota
|
||||
ExecutionRequested DistributedQueryExecutionStatus = iota
|
||||
ExecutionSucceeded DistributedQueryExecutionStatus = iota
|
||||
ExecutionFailed DistributedQueryExecutionStatus = iota
|
||||
)
|
||||
|
||||
type DistributedQueryExecution struct {
|
||||
HostID uint
|
||||
DistributedQueryID uint
|
||||
Status DistributedQueryExecutionStatus
|
||||
Error string `gorm:"size:1024"`
|
||||
ExecutionDuration time.Duration
|
||||
}
|
||||
|
||||
type Pack struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string `gorm:"not null;unique_index:idx_pack_unique_name"`
|
||||
Platform string
|
||||
Queries []ScheduledQuery
|
||||
DiscoveryQueries []DiscoveryQuery
|
||||
}
|
||||
|
||||
type DiscoveryQuery struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Query string `gorm:"size:1024" gorm:"not null"`
|
||||
}
|
||||
|
||||
type Host struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
NodeKey string `gorm:"unique_index:idx_host_unique_nodekey"`
|
||||
HostName string
|
||||
UUID string `gorm:"unique_index:idx_host_unique_uuid"`
|
||||
IPAddress string
|
||||
Platform string
|
||||
Labels []*Label `gorm:"many2many:host_labels;"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string `gorm:"not null;unique_index:idx_label_unique_name"`
|
||||
Query string
|
||||
Hosts []Host
|
||||
}
|
||||
|
||||
type Option struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Key string `gorm:"not null;unique_index:idx_option_unique_key"`
|
||||
Value string `gorm:"not null"`
|
||||
Platform string
|
||||
}
|
||||
|
||||
type DecoratorType int
|
||||
|
||||
const (
|
||||
DecoratorLoad DecoratorType = iota
|
||||
DecoratorAlways DecoratorType = iota
|
||||
DecoratorInterval DecoratorType = iota
|
||||
)
|
||||
|
||||
type Decorator struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Type DecoratorType `gorm:"not null"`
|
||||
Interval int
|
||||
Query string
|
||||
}
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
package osquery
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type OsqueryEnrollPostBody struct {
|
||||
EnrollSecret string `json:"enroll_secret" binding:"required"`
|
||||
}
|
||||
|
||||
type OsqueryConfigPostBody struct {
|
||||
NodeKey string `json:"node_key" binding:"required"`
|
||||
}
|
||||
|
||||
type OsqueryLogPostBody struct {
|
||||
NodeKey string `json:"node_key" binding:"required"`
|
||||
LogType string `json:"log_type" binding:"required"`
|
||||
Data []map[string]interface{} `json:"data" binding:"required"`
|
||||
}
|
||||
|
||||
type OsqueryResultLog struct {
|
||||
Name string `json:"name"`
|
||||
HostIdentifier string `json:"hostIdentifier"`
|
||||
UnixTime string `json:"unixTime"`
|
||||
CalendarTime string `json:"calendarTime"`
|
||||
Columns map[string]string `json:"columns"`
|
||||
Action string `json:"action"`
|
||||
}
|
||||
|
||||
type OsqueryStatusLog struct {
|
||||
Severity string `json:"severity"`
|
||||
Filename string `json:"filename"`
|
||||
Line string `json:"line"`
|
||||
Message string `json:"message"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type OsqueryDistributedReadPostBody struct {
|
||||
NodeKey string `json:"node_key" binding:"required"`
|
||||
}
|
||||
|
||||
type OsqueryDistributedWritePostBody struct {
|
||||
NodeKey string `json:"node_key" binding:"required"`
|
||||
Queries map[string][]map[string]string `json:"queries" binding:"required"`
|
||||
}
|
||||
|
||||
func OsqueryEnroll(c *gin.Context) {
|
||||
var body OsqueryEnrollPostBody
|
||||
err := c.BindJSON(&body)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error parsing OsqueryEnroll POST body: %s", err.Error())
|
||||
return
|
||||
}
|
||||
logrus.Debugf("OsqueryEnroll: %s", body.EnrollSecret)
|
||||
|
||||
c.JSON(http.StatusOK,
|
||||
gin.H{
|
||||
"node_key": "7",
|
||||
"node_invalid": false,
|
||||
})
|
||||
}
|
||||
|
||||
func OsqueryConfig(c *gin.Context) {
|
||||
var body OsqueryConfigPostBody
|
||||
err := c.BindJSON(&body)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error parsing OsqueryConfig POST body: %s", err.Error())
|
||||
return
|
||||
}
|
||||
logrus.Debugf("OsqueryConfig: %s", body.NodeKey)
|
||||
|
||||
c.JSON(http.StatusOK,
|
||||
gin.H{
|
||||
"schedule": map[string]map[string]interface{}{
|
||||
"time": {
|
||||
"query": "select * from time;",
|
||||
"interval": 1,
|
||||
},
|
||||
},
|
||||
"node_invalid": false,
|
||||
})
|
||||
}
|
||||
|
||||
func OsqueryLog(c *gin.Context) {
|
||||
var body OsqueryLogPostBody
|
||||
err := c.BindJSON(&body)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error parsing OsqueryLog POST body: %s", err.Error())
|
||||
return
|
||||
}
|
||||
logrus.Debugf("OsqueryLog: %s", body.LogType)
|
||||
|
||||
if body.LogType == "status" {
|
||||
for _, data := range body.Data {
|
||||
var log OsqueryStatusLog
|
||||
|
||||
severity, ok := data["severity"].(string)
|
||||
if ok {
|
||||
log.Severity = severity
|
||||
} else {
|
||||
logrus.Error("Error asserting the type of status log severity")
|
||||
}
|
||||
|
||||
filename, ok := data["filename"].(string)
|
||||
if ok {
|
||||
log.Filename = filename
|
||||
} else {
|
||||
logrus.Error("Error asserting the type of status log filename")
|
||||
}
|
||||
|
||||
line, ok := data["line"].(string)
|
||||
if ok {
|
||||
log.Line = line
|
||||
} else {
|
||||
logrus.Error("Error asserting the type of status log line")
|
||||
}
|
||||
|
||||
message, ok := data["message"].(string)
|
||||
if ok {
|
||||
log.Message = message
|
||||
} else {
|
||||
logrus.Error("Error asserting the type of status log message")
|
||||
}
|
||||
|
||||
version, ok := data["version"].(string)
|
||||
if ok {
|
||||
log.Version = version
|
||||
} else {
|
||||
logrus.Error("Error asserting the type of status log version")
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"node_key": body.NodeKey,
|
||||
"severity": log.Severity,
|
||||
"filename": log.Filename,
|
||||
"line": log.Line,
|
||||
"version": log.Version,
|
||||
}).Info(log.Message)
|
||||
}
|
||||
} else if body.LogType == "result" {
|
||||
// TODO: handle all of the different kinds of results logs
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK,
|
||||
gin.H{
|
||||
"node_invalid": false,
|
||||
})
|
||||
}
|
||||
|
||||
func OsqueryDistributedRead(c *gin.Context) {
|
||||
var body OsqueryDistributedReadPostBody
|
||||
err := c.BindJSON(&body)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error parsing OsqueryDistributedRead POST body: %s", err.Error())
|
||||
return
|
||||
}
|
||||
logrus.Debugf("OsqueryDistributedRead: %s", body.NodeKey)
|
||||
|
||||
c.JSON(http.StatusOK,
|
||||
gin.H{
|
||||
"queries": map[string]string{
|
||||
"id1": "select * from osquery_info",
|
||||
},
|
||||
"node_invalid": false,
|
||||
})
|
||||
}
|
||||
|
||||
func OsqueryDistributedWrite(c *gin.Context) {
|
||||
var body OsqueryDistributedWritePostBody
|
||||
err := c.BindJSON(&body)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error parsing OsqueryDistributedWrite POST body: %s", err.Error())
|
||||
return
|
||||
}
|
||||
logrus.Debugf("OsqueryDistributedWrite: %s", body.NodeKey)
|
||||
c.JSON(http.StatusOK,
|
||||
gin.H{
|
||||
"node_invalid": false,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,30 +1,32 @@
|
|||
package sessions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/kolide/kolide-ose/errors"
|
||||
)
|
||||
|
||||
const publicErrorMessage string = "Session error"
|
||||
|
||||
var (
|
||||
// An error returned by SessionBackend.Get() if no session record was found
|
||||
// in the database
|
||||
ErrNoActiveSession = errors.New("Active session is not present in the database")
|
||||
ErrNoActiveSession = errors.New(publicErrorMessage, "Active session is not present in the database")
|
||||
|
||||
// An error returned by SessionBackend methods when no session object has
|
||||
// been created yet but the requested action requires one
|
||||
ErrSessionNotCreated = errors.New("The session has not been created")
|
||||
ErrSessionNotCreated = errors.New(publicErrorMessage, "The session has not been created")
|
||||
|
||||
// An error returned by SessionBackend.Get() when a session is requested but
|
||||
// it has expired
|
||||
ErrSessionExpired = errors.New("The session has expired")
|
||||
ErrSessionExpired = errors.New(publicErrorMessage, "The session has expired")
|
||||
|
||||
// An error returned by SessionBackend which indicates that the token
|
||||
// or it's content were malformed
|
||||
ErrSessionMalformed = errors.New("The session token was malformed")
|
||||
ErrSessionMalformed = errors.New(publicErrorMessage, "The session token was malformed")
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -205,7 +207,7 @@ func ParseJWT(token string) (*jwt.Token, error) {
|
|||
return jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
|
||||
method, ok := t.Method.(*jwt.SigningMethodHMAC)
|
||||
if !ok || method != jwt.SigningMethodHS256 {
|
||||
return nil, errors.New("Unexpected signing method")
|
||||
return nil, errors.New(publicErrorMessage, "Unexpected signing method")
|
||||
}
|
||||
return []byte(jwtKey), nil
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue