Skip to content

Commit

Permalink
Wire tsdb.Authorize back into query handler
Browse files Browse the repository at this point in the history
  • Loading branch information
gunnaraasen committed Jul 16, 2015
1 parent e490ea0 commit c644613
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 37 deletions.
10 changes: 10 additions & 0 deletions services/httpd/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type Handler struct {
}

QueryExecutor interface {
Authorize(u *meta.UserInfo, q *influxql.Query, db string) error
ExecuteQuery(q *influxql.Query, db string, chunkSize int) (<-chan *influxql.Result, error)
}

Expand Down Expand Up @@ -220,6 +221,15 @@ func (h *Handler) serveQuery(w http.ResponseWriter, r *http.Request, user *meta.
return
}

// Check authorization.
if h.requireAuthentication {
err = h.QueryExecutor.Authorize(user, query, db)
if err != nil {
httpError(w, "error authorizing query: "+err.Error(), pretty, http.StatusUnauthorized)
return
}
}

// Parse chunk size. Use default if not provided or unparsable.
chunked := (q.Get("chunked") == "true")
chunkSize := DefaultChunkSize
Expand Down
14 changes: 11 additions & 3 deletions services/httpd/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,12 +223,12 @@ func TestHandler_Query_ErrInvalidQuery(t *testing.T) {
// Ensure the handler returns a status 401 if the user is not authorized.
func TestHandler_Query_ErrUnauthorized(t *testing.T) {
h := NewHandler(false)
h.QueryExecutor.ExecuteQueryFn = func(q *influxql.Query, db string, chunkSize int) (<-chan *influxql.Result, error) {
return nil, meta.NewAuthError("marker")
h.QueryExecutor.AuthorizeFn = func(u *meta.UserInfo, q *influxql.Query, db string) error {
return errors.New("marker")
}

w := httptest.NewRecorder()
h.ServeHTTP(w, MustNewJSONRequest("GET", "/query?db=foo&q=SHOW+SERIES+FROM+bar", nil))
h.ServeHTTP(w, MustNewJSONRequest("GET", "/query?u=bar&db=foo&q=SHOW+SERIES+FROM+bar", nil))
if w.Code != http.StatusUnauthorized {
t.Fatalf("unexpected status: %d", w.Code)
}
Expand Down Expand Up @@ -264,6 +264,7 @@ func TestHandler_Query_ErrResult(t *testing.T) {
}
}

<<<<<<< HEAD
// Ensure the handler returns a status 401 if an auth error is returned from the result.
func TestHandler_Query_Result_ErrUnauthorized(t *testing.T) {
h := NewHandler(false)
Expand Down Expand Up @@ -330,6 +331,8 @@ func TestHandler_Mapper_ShardDoesNotExist(t *testing.T) {
}
}

=======
>>>>>>> Wire tsdb.Authorize back into query handler
func TestMarshalJSON_NoPretty(t *testing.T) {
if b := httpd.MarshalJSON(struct {
Name string `json:"name"`
Expand Down Expand Up @@ -462,9 +465,14 @@ func (s *HandlerMetaStore) Users() ([]meta.UserInfo, error) {

// HandlerQueryExecutor is a mock implementation of Handler.QueryExecutor.
type HandlerQueryExecutor struct {
AuthorizeFn func(u *meta.UserInfo, q *influxql.Query, db string) error
ExecuteQueryFn func(q *influxql.Query, db string, chunkSize int) (<-chan *influxql.Result, error)
}

func (e *HandlerQueryExecutor) Authorize(u *meta.UserInfo, q *influxql.Query, db string) error {
return e.AuthorizeFn(u, q, db)
}

func (e *HandlerQueryExecutor) ExecuteQuery(q *influxql.Query, db string, chunkSize int) (<-chan *influxql.Result, error) {
return e.ExecuteQueryFn(q, db, chunkSize)
}
Expand Down
67 changes: 33 additions & 34 deletions tsdb/query_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,27 +60,24 @@ func NewQueryExecutor(store *Store) *QueryExecutor {
// If no user is provided it will return an error unless the query's first statement is to create
// a root user.
func (q *QueryExecutor) Authorize(u *meta.UserInfo, query *influxql.Query, database string) error {
const authErrLogFmt = "unauthorized request | user: %q | query: %q | database %q\n"

// Special case if no users exist.
if count, err := q.MetaStore.UserCount(); count == 0 && err == nil {
// Get the first statement in the query.
stmt := query.Statements[0]
// First statement must create a root user.
if cu, ok := stmt.(*influxql.CreateUserStatement); !ok ||
cu.Privilege == nil ||
*cu.Privilege != influxql.AllPrivileges {
return ErrAuthorize{text: "no users exist. create root user first or disable authentication"}
// Ensure there is at least one statement.
if len(query.Statements) > 0 {
// First statement in the query must create a user with admin privilege.
cu, ok := query.Statements[0].(*influxql.CreateUserStatement)
if ok && cu.Admin == true {
return nil
}
}
return nil
return NewErrAuthorize(q, query, "", database, "create admin user first or disable authentication")
}

if u == nil {
q.Logger.Printf(authErrLogFmt, "", query.String(), database)
return ErrAuthorize{text: "no user provided"}
return NewErrAuthorize(q, query, "", database, "no user provided")
}

// Cluster admins can do anything.
// Admin privilege allows the user to execute all statements.
if u.Admin {
return nil
}
Expand All @@ -103,23 +100,13 @@ func (q *QueryExecutor) Authorize(u *meta.UserInfo, query *influxql.Query, datab
// Use the db name specified by the statement or the db
// name passed by the caller if one wasn't specified by
// the statement.
dbname := p.Name
if dbname == "" {
dbname = database
db := p.Name
if db == "" {
db = database
}

// Check if user has required privilege.
if !u.Authorize(p.Privilege, dbname) {
var msg string
if dbname == "" {
msg = "requires cluster admin"
} else {
msg = fmt.Sprintf("requires %s privilege on %s", p.Privilege.String(), dbname)
}
q.Logger.Printf(authErrLogFmt, u.Name, query.String(), database)
return ErrAuthorize{
text: fmt.Sprintf("%s not authorized to execute '%s'. %s", u.Name, stmt.String(), msg),
}
if !u.Authorize(p.Privilege, db) {
msg := fmt.Sprintf("statement '%s', requires %s on %s", stmt, p.Privilege.String(), db)
return NewErrAuthorize(q, query, u.Name, database, msg)
}
}
}
Expand Down Expand Up @@ -998,17 +985,29 @@ func (q *QueryExecutor) executeShowDiagnosticsStatement(stmt *influxql.ShowDiagn

// ErrAuthorize represents an authorization error.
type ErrAuthorize struct {
text string
q *QueryExecutor
query *influxql.Query
user string
database string
message string
}

const authErrLogFmt string = "unauthorized request | user: %q | query: %q | database %q\n"

// newAuthorizationError returns a new instance of AuthorizationError.
func NewErrAuthorize(qe *QueryExecutor, q *influxql.Query, u, db, m string) *ErrAuthorize {
return &ErrAuthorize{q: qe, query: q, user: u, database: db, message: m}
}

// Error returns the text of the error.
func (e ErrAuthorize) Error() string {
return e.text
e.q.Logger.Printf(authErrLogFmt, e.user, e.query.String(), e.database)
if e.user == "" {
return fmt.Sprint(e.message)
}
return fmt.Sprintf("%s not authorized to execute %s", e.user, e.message)
}

// authorize satisfies isAuthorizationError
func (ErrAuthorize) authorize() {}

var (
// ErrInvalidQuery is returned when executing an unknown query type.
ErrInvalidQuery = errors.New("invalid query")
Expand Down

0 comments on commit c644613

Please sign in to comment.