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
This commit is contained in:
Mike Arpaia 2017-01-09 08:10:02 -07:00 committed by GitHub
parent cc5aa3395d
commit a047ef2211
8 changed files with 68 additions and 25 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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")

View File

@ -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"])
}

View File

@ -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"},
},

View File

@ -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)

View File

@ -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")

View File

@ -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
}{
{