From a047ef2211df455c16e837a5fcccad99cb755d5b Mon Sep 17 00:00:00 2001 From: Mike Arpaia Date: Mon, 9 Jan 2017 08:10:02 -0700 Subject: [PATCH] Quick contexts additions (#739) * Defining a concrete type for session tokens * More rightish vc.IsLoggedIn() * using type conversion instead of a method call * include sessions in test viewer contexts --- server/contexts/token/token.go | 16 +++++--- server/contexts/viewer/viewer.go | 11 +++++- server/service/endpoint_campaigns.go | 2 +- server/service/endpoint_middleware.go | 4 +- server/service/endpoint_middleware_test.go | 45 ++++++++++++++++------ server/service/service_users_test.go | 7 +++- server/websocket/websocket.go | 5 ++- server/websocket/websocket_test.go | 3 +- 8 files changed, 68 insertions(+), 25 deletions(-) diff --git a/server/contexts/token/token.go b/server/contexts/token/token.go index 4d4a9c6f7f..1725501e9a 100644 --- a/server/contexts/token/token.go +++ b/server/contexts/token/token.go @@ -3,28 +3,32 @@ package token import ( - "golang.org/x/net/context" "net/http" "strings" + + "golang.org/x/net/context" ) type key int const tokenKey key = 0 +// Token is the concrete type which represents kolide session tokens +type Token string + // FromHTTPRequest extracts an Authorization // from an HTTP header if present. -func FromHTTPRequest(r *http.Request) string { +func FromHTTPRequest(r *http.Request) Token { headers := r.Header.Get("Authorization") headerParts := strings.Split(headers, " ") if len(headerParts) != 2 || strings.ToUpper(headerParts[0]) != "BEARER" { return "" } - return headerParts[1] + return Token(headerParts[1]) } // NewContext returns a new context carrying the Authorization Bearer token. -func NewContext(ctx context.Context, token string) context.Context { +func NewContext(ctx context.Context, token Token) context.Context { if token == "" { return ctx } @@ -32,7 +36,7 @@ func NewContext(ctx context.Context, token string) context.Context { } // FromContext extracts the Authorization Bearer token if present. -func FromContext(ctx context.Context) (string, bool) { - token, ok := ctx.Value(tokenKey).(string) +func FromContext(ctx context.Context) (Token, bool) { + token, ok := ctx.Value(tokenKey).(Token) return token, ok } diff --git a/server/contexts/viewer/viewer.go b/server/contexts/viewer/viewer.go index 3654986cac..5f28ec619b 100644 --- a/server/contexts/viewer/viewer.go +++ b/server/contexts/viewer/viewer.go @@ -77,7 +77,16 @@ func (v Viewer) SessionID() uint { // account func (v Viewer) IsLoggedIn() bool { if v.User != nil { - return v.User.Enabled + if !v.User.Enabled { + return false + } + } + if v.Session != nil { + // Without having access to a service to call GetInfoAboutSession(id), + // we can't synchronously check the database here. + if v.Session.ID != 0 { + return true + } } return false } diff --git a/server/service/endpoint_campaigns.go b/server/service/endpoint_campaigns.go index a93abb3422..5859eed61c 100644 --- a/server/service/endpoint_campaigns.go +++ b/server/service/endpoint_campaigns.go @@ -63,7 +63,7 @@ func makeStreamDistributedQueryCampaignResultsHandler(svc kolide.Service, jwtKey } // Authenticate with the token - vc, err := authViewer(context.Background(), jwtKey, string(token), svc) + vc, err := authViewer(context.Background(), jwtKey, token, svc) if err != nil || !vc.CanPerformActions() { logger.Log("err", err, "msg", "unauthorized viewer") conn.WriteJSONError("unauthorized") diff --git a/server/service/endpoint_middleware.go b/server/service/endpoint_middleware.go index 3110747590..d555a82c43 100644 --- a/server/service/endpoint_middleware.go +++ b/server/service/endpoint_middleware.go @@ -85,8 +85,8 @@ func authenticatedUser(jwtKey string, svc kolide.Service, next endpoint.Endpoint } // authViewer creates an authenticated viewer by validating a JWT token. -func authViewer(ctx context.Context, jwtKey string, bearerToken string, svc kolide.Service) (*viewer.Viewer, error) { - jwtToken, err := jwt.Parse(bearerToken, func(token *jwt.Token) (interface{}, error) { +func authViewer(ctx context.Context, jwtKey string, bearerToken token.Token, svc kolide.Service) (*viewer.Viewer, error) { + jwtToken, err := jwt.Parse(string(bearerToken), func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } diff --git a/server/service/endpoint_middleware_test.go b/server/service/endpoint_middleware_test.go index f2f87b9ac0..63b236ceb0 100644 --- a/server/service/endpoint_middleware_test.go +++ b/server/service/endpoint_middleware_test.go @@ -18,11 +18,34 @@ import ( // permissions to access or modify resources func TestEndpointPermissions(t *testing.T) { req := struct{}{} - ds, _ := inmem.New(config.TestConfig()) + ds, err := inmem.New(config.TestConfig()) + assert.Nil(t, err) + createTestUsers(t, ds) - admin1, _ := ds.User("admin1") - user1, _ := ds.User("user1") - user2, _ := ds.User("user2") + + admin1, err := ds.User("admin1") + assert.Nil(t, err) + admin1Session, err := ds.NewSession(&kolide.Session{ + UserID: admin1.ID, + Key: "admin1", + }) + assert.Nil(t, err) + + user1, err := ds.User("user1") + assert.Nil(t, err) + user1Session, err := ds.NewSession(&kolide.Session{ + UserID: user1.ID, + Key: "user1", + }) + assert.Nil(t, err) + + user2, err := ds.User("user2") + assert.Nil(t, err) + user2Session, err := ds.NewSession(&kolide.Session{ + UserID: user2.ID, + Key: "user2", + }) + assert.Nil(t, err) user2.Enabled = false e := endpoint.Nop // a test endpoint @@ -51,36 +74,36 @@ func TestEndpointPermissions(t *testing.T) { }, { endpoint: mustBeAdmin(e), - vc: &viewer.Viewer{User: admin1}, + vc: &viewer.Viewer{User: admin1, Session: admin1Session}, }, { endpoint: mustBeAdmin(e), - vc: &viewer.Viewer{User: user1}, + vc: &viewer.Viewer{User: user1, Session: user1Session}, wantErr: permissionError{message: "must be an admin"}, }, { endpoint: canModifyUser(e), - vc: &viewer.Viewer{User: admin1}, + vc: &viewer.Viewer{User: admin1, Session: admin1Session}, }, { endpoint: canModifyUser(e), - vc: &viewer.Viewer{User: user1}, + vc: &viewer.Viewer{User: user1, Session: user1Session}, wantErr: permissionError{message: "no write permissions on user"}, }, { endpoint: canModifyUser(e), - vc: &viewer.Viewer{User: user1}, + vc: &viewer.Viewer{User: user1, Session: user1Session}, requestID: admin1.ID, wantErr: permissionError{message: "no write permissions on user"}, }, { endpoint: canReadUser(e), - vc: &viewer.Viewer{User: user1}, + vc: &viewer.Viewer{User: user1, Session: user1Session}, requestID: admin1.ID, }, { endpoint: canReadUser(e), - vc: &viewer.Viewer{User: user2}, + vc: &viewer.Viewer{User: user2, Session: user2Session}, requestID: admin1.ID, wantErr: permissionError{message: "no read permissions on user"}, }, diff --git a/server/service/service_users_test.go b/server/service/service_users_test.go index 12ee58acc1..58072bf6a1 100644 --- a/server/service/service_users_test.go +++ b/server/service/service_users_test.go @@ -25,9 +25,14 @@ func TestAuthenticatedUser(t *testing.T) { assert.Nil(t, err) admin1, err := ds.User("admin1") assert.Nil(t, err) + admin1Session, err := ds.NewSession(&kolide.Session{ + UserID: admin1.ID, + Key: "admin1", + }) + assert.Nil(t, err) ctx := context.Background() - ctx = viewer.NewContext(ctx, viewer.Viewer{User: admin1}) + ctx = viewer.NewContext(ctx, viewer.Viewer{User: admin1, Session: admin1Session}) user, err := svc.AuthenticatedUser(ctx) assert.Nil(t, err) assert.Equal(t, user, admin1) diff --git a/server/websocket/websocket.go b/server/websocket/websocket.go index ef1b0d0126..b86a14dd5f 100644 --- a/server/websocket/websocket.go +++ b/server/websocket/websocket.go @@ -8,6 +8,7 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/kolide/kolide-ose/server/contexts/token" "github.com/pkg/errors" ) @@ -112,13 +113,13 @@ func (c *Conn) ReadJSONMessage() (*JSONMessage, error) { // authData defines the data used to authenticate a Kolide frontend client over // a websocket connection. type authData struct { - Token string `json:"token"` + Token token.Token `json:"token"` } // ReadAuthToken reads from the websocket, returning an auth token embedded in // a JSONMessage with type "auth" and data that can be unmarshalled to // authData. -func (c *Conn) ReadAuthToken() (string, error) { +func (c *Conn) ReadAuthToken() (token.Token, error) { msg, err := c.ReadJSONMessage() if err != nil { return "", errors.Wrap(err, "read auth token") diff --git a/server/websocket/websocket_test.go b/server/websocket/websocket_test.go index a18e13481c..6620c7e9c8 100644 --- a/server/websocket/websocket_test.go +++ b/server/websocket/websocket_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/kolide/kolide-ose/server/contexts/token" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -238,7 +239,7 @@ func TestReadAuthToken(t *testing.T) { var cases = []struct { typ string data authData - token string + token token.Token err error }{ {