2016-09-05 20:03:58 +00:00
|
|
|
package server
|
2016-08-28 03:59:17 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2016-09-08 01:24:11 +00:00
|
|
|
"strings"
|
2016-08-28 03:59:17 +00:00
|
|
|
|
2016-09-08 01:24:11 +00:00
|
|
|
jwt "github.com/dgrijalva/jwt-go"
|
|
|
|
"github.com/dgrijalva/jwt-go/request"
|
2016-08-28 03:59:17 +00:00
|
|
|
kitlog "github.com/go-kit/kit/log"
|
2016-09-08 01:24:11 +00:00
|
|
|
kithttp "github.com/go-kit/kit/transport/http"
|
2016-08-28 03:59:17 +00:00
|
|
|
"github.com/kolide/kolide-ose/kolide"
|
2016-09-03 17:25:16 +00:00
|
|
|
"golang.org/x/net/context"
|
2016-08-28 03:59:17 +00:00
|
|
|
)
|
|
|
|
|
2016-09-01 04:51:38 +00:00
|
|
|
// authentication error
|
2016-08-28 03:59:17 +00:00
|
|
|
type authError struct {
|
2016-09-20 19:22:54 +00:00
|
|
|
reason string
|
|
|
|
// client reason is used to provide
|
|
|
|
// a different error message to the client
|
|
|
|
// when security is a concern
|
|
|
|
clientReason string
|
2016-08-28 03:59:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e authError) Error() string {
|
2016-09-20 19:22:54 +00:00
|
|
|
return e.reason
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e authError) AuthError() string {
|
|
|
|
if e.clientReason != "" {
|
|
|
|
return e.clientReason
|
2016-08-28 03:59:17 +00:00
|
|
|
}
|
2016-09-20 19:22:54 +00:00
|
|
|
return "authorization error"
|
2016-08-28 03:59:17 +00:00
|
|
|
}
|
|
|
|
|
2016-09-01 04:51:38 +00:00
|
|
|
// forbidden, set when user is authenticated, but not allowd to perform action
|
|
|
|
type forbiddenError struct {
|
|
|
|
message string
|
2016-09-15 14:52:17 +00:00
|
|
|
fields []string
|
2016-09-01 04:51:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e forbiddenError) Error() string {
|
|
|
|
if e.message == "" {
|
|
|
|
return "unauthorized"
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("unauthorized: %s", e.message)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ViewerContext is a struct which represents the ability for an execution
|
2016-08-28 03:59:17 +00:00
|
|
|
// context to participate in certain actions. Most often, a ViewerContext is
|
|
|
|
// associated with an application user, but a ViewerContext can represent a
|
|
|
|
// variety of other execution contexts as well (script, test, etc). The main
|
|
|
|
// purpose of a ViewerContext is to assist in the authorization of sensitive
|
|
|
|
// actions.
|
|
|
|
type viewerContext struct {
|
2016-09-08 01:24:11 +00:00
|
|
|
user *kolide.User
|
|
|
|
session *kolide.Session
|
2016-08-28 03:59:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// IsAdmin indicates whether or not the current user can perform administrative
|
|
|
|
// actions.
|
|
|
|
func (vc *viewerContext) IsAdmin() bool {
|
|
|
|
if vc.user != nil {
|
|
|
|
return vc.user.Admin && vc.user.Enabled
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// UserID is a helper that enables quick access to the user ID of the current
|
|
|
|
// user.
|
|
|
|
func (vc *viewerContext) UserID() uint {
|
|
|
|
if vc.user != nil {
|
|
|
|
return vc.user.ID
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2016-09-08 01:24:11 +00:00
|
|
|
func (vc *viewerContext) SessionID() uint {
|
|
|
|
if vc.session != nil {
|
|
|
|
return vc.session.ID
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2016-08-28 03:59:17 +00:00
|
|
|
// IsLoggedIn determines whether or not the current VC is attached to a user
|
|
|
|
// account
|
|
|
|
func (vc *viewerContext) IsLoggedIn() bool {
|
|
|
|
return vc.user != nil && vc.user.Enabled
|
|
|
|
}
|
|
|
|
|
|
|
|
// CanPerformActions returns a bool indicating the current user's ability to
|
|
|
|
// perform the most basic actions on the site
|
|
|
|
func (vc *viewerContext) CanPerformActions() bool {
|
2016-09-08 22:57:05 +00:00
|
|
|
return vc.IsLoggedIn() && !vc.user.AdminForcedPasswordReset
|
2016-08-28 03:59:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// CanPerformReadActionsOnUser returns a bool indicating the current user's
|
|
|
|
// ability to perform read actions on the given user
|
2016-09-01 04:51:38 +00:00
|
|
|
func (vc *viewerContext) CanPerformReadActionOnUser(uid uint) bool {
|
|
|
|
return vc.CanPerformActions() || (vc.IsLoggedIn() && vc.IsUserID(uid))
|
2016-08-28 03:59:17 +00:00
|
|
|
}
|
|
|
|
|
2016-08-28 15:12:04 +00:00
|
|
|
// CanPerformWriteActionOnUser returns a bool indicating the current user's
|
|
|
|
// ability to perform write actions on the given user
|
2016-09-01 04:51:38 +00:00
|
|
|
func (vc *viewerContext) CanPerformWriteActionOnUser(uid uint) bool {
|
|
|
|
return vc.CanPerformActions() && (vc.IsUserID(uid) || vc.IsAdmin())
|
2016-08-28 15:12:04 +00:00
|
|
|
}
|
|
|
|
|
2016-08-28 03:59:17 +00:00
|
|
|
// IsUserID returns true if the given user id the same as the user which is
|
|
|
|
// represented by this ViewerContext
|
|
|
|
func (vc *viewerContext) IsUserID(id uint) bool {
|
|
|
|
if vc.UserID() == id {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-09-08 01:24:11 +00:00
|
|
|
// newViewerContext generates a ViewerContext given a session
|
|
|
|
func newViewerContext(user *kolide.User, session *kolide.Session) *viewerContext {
|
2016-08-28 03:59:17 +00:00
|
|
|
return &viewerContext{
|
2016-09-08 01:24:11 +00:00
|
|
|
user: user,
|
|
|
|
session: session,
|
2016-08-28 03:59:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-04 05:13:42 +00:00
|
|
|
// emptyVC is a utility which generates an empty ViewerContext. This is often
|
2016-08-28 03:59:17 +00:00
|
|
|
// used to represent users which are not logged in.
|
|
|
|
func emptyVC() *viewerContext {
|
|
|
|
return &viewerContext{}
|
|
|
|
}
|
|
|
|
|
2016-09-08 01:24:11 +00:00
|
|
|
func viewerContextFromContext(ctx context.Context) (*viewerContext, error) {
|
|
|
|
vc, ok := ctx.Value("viewerContext").(*viewerContext)
|
|
|
|
if !ok {
|
|
|
|
return nil, errNoContext
|
|
|
|
}
|
|
|
|
return vc, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// setViewerContext updates the context with a viewerContext,
|
|
|
|
// which holds the currently logged in user
|
|
|
|
func setViewerContext(svc kolide.Service, ds kolide.Datastore, jwtKey string, logger kitlog.Logger) kithttp.RequestFunc {
|
|
|
|
return func(ctx context.Context, r *http.Request) context.Context {
|
|
|
|
token, err := request.ParseFromRequest(r, request.AuthorizationHeaderExtractor, func(token *jwt.Token) (interface{}, error) {
|
|
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
|
|
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
|
|
|
}
|
2016-09-12 15:31:58 +00:00
|
|
|
return []byte(jwtKey), nil
|
2016-09-08 01:24:11 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
2016-09-15 14:52:17 +00:00
|
|
|
if err != request.ErrNoTokenInRequest {
|
|
|
|
// all unauthenticated requests (login,logout,passwordreset) result in the
|
|
|
|
// request.ErrNoTokenInRequest error. we can ignore logging it
|
|
|
|
logger.Log("err", err, "error-source", "setViewerContext")
|
|
|
|
}
|
2016-09-08 01:24:11 +00:00
|
|
|
return context.WithValue(ctx, "viewerContext", emptyVC())
|
|
|
|
}
|
|
|
|
|
|
|
|
claims, ok := token.Claims.(jwt.MapClaims)
|
|
|
|
if !ok {
|
|
|
|
return context.WithValue(ctx, "viewerContext", emptyVC())
|
|
|
|
}
|
|
|
|
|
|
|
|
sessionKeyClaim, ok := claims["session_key"]
|
|
|
|
if !ok {
|
|
|
|
return context.WithValue(ctx, "viewerContext", emptyVC())
|
|
|
|
}
|
|
|
|
|
|
|
|
sessionKey, ok := sessionKeyClaim.(string)
|
|
|
|
if !ok {
|
|
|
|
return context.WithValue(ctx, "viewerContext", emptyVC())
|
|
|
|
}
|
|
|
|
|
2016-09-14 16:11:06 +00:00
|
|
|
session, err := svc.GetSessionByKey(ctx, sessionKey)
|
2016-09-08 01:24:11 +00:00
|
|
|
if err != nil {
|
|
|
|
switch err {
|
|
|
|
case kolide.ErrNoActiveSession:
|
|
|
|
// If the code path got this far, it's likely that the user was logged
|
|
|
|
// in some time in the past, but their session has been expired since
|
|
|
|
// their last usage of the application
|
|
|
|
return context.WithValue(ctx, "viewerContext", emptyVC())
|
|
|
|
default:
|
|
|
|
return context.WithValue(ctx, "viewerContext", emptyVC())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
user, err := svc.User(ctx, session.UserID)
|
|
|
|
if err != nil {
|
|
|
|
logger.Log("err", err, "error-source", "setViewerContext")
|
|
|
|
return context.WithValue(ctx, "viewerContext", emptyVC())
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx = context.WithValue(ctx, "viewerContext", newViewerContext(user, session))
|
|
|
|
logger.Log("msg", "viewer context set", "user", user.ID)
|
|
|
|
// get the user-id for request
|
|
|
|
if strings.Contains(r.URL.Path, "users/") {
|
|
|
|
ctx = withUserIDFromRequest(r, ctx)
|
|
|
|
}
|
|
|
|
return ctx
|
2016-08-28 03:59:17 +00:00
|
|
|
}
|
2016-09-08 01:24:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func withUserIDFromRequest(r *http.Request, ctx context.Context) context.Context {
|
|
|
|
id, _ := idFromRequest(r, "id")
|
|
|
|
return context.WithValue(ctx, "request-id", id)
|
2016-08-28 03:59:17 +00:00
|
|
|
}
|