1package meta
2
3import (
4	"fmt"
5
6	"github.com/influxdata/influxql"
7)
8
9// QueryAuthorizer determines whether a user is authorized to execute a given query.
10type QueryAuthorizer struct {
11	Client *Client
12}
13
14// NewQueryAuthorizer returns a new instance of QueryAuthorizer.
15func NewQueryAuthorizer(c *Client) *QueryAuthorizer {
16	return &QueryAuthorizer{
17		Client: c,
18	}
19}
20
21// AuthorizeQuery authorizes u to execute q on database.
22// Database can be "" for queries that do not require a database.
23// If no user is provided it will return an error unless the query's first statement is to create
24// a root user.
25func (a *QueryAuthorizer) AuthorizeQuery(u User, query *influxql.Query, database string) error {
26	// Special case if no users exist.
27	if n := a.Client.UserCount(); n == 0 {
28		// Ensure there is at least one statement.
29		if len(query.Statements) > 0 {
30			// First statement in the query must create a user with admin privilege.
31			cu, ok := query.Statements[0].(*influxql.CreateUserStatement)
32			if ok && cu.Admin {
33				return nil
34			}
35		}
36		return &ErrAuthorize{
37			Query:    query,
38			Database: database,
39			Message:  "create admin user first or disable authentication",
40		}
41	}
42
43	if u == nil {
44		return &ErrAuthorize{
45			Query:    query,
46			Database: database,
47			Message:  "no user provided",
48		}
49	}
50
51	return u.AuthorizeQuery(database, query)
52}
53
54func (a *QueryAuthorizer) AuthorizeDatabase(u User, priv influxql.Privilege, database string) error {
55	if u == nil {
56		return &ErrAuthorize{
57			Database: database,
58			Message:  "no user provided",
59		}
60	}
61
62	if !u.AuthorizeDatabase(priv, database) {
63		return &ErrAuthorize{
64			Database: database,
65			Message:  fmt.Sprintf("user %q, requires %s for database %q", u.ID(), priv.String(), database),
66		}
67	}
68
69	return nil
70}
71
72func (u *UserInfo) AuthorizeQuery(database string, query *influxql.Query) error {
73
74	// Admin privilege allows the user to execute all statements.
75	if u.Admin {
76		return nil
77	}
78
79	// Check each statement in the query.
80	for _, stmt := range query.Statements {
81		// Get the privileges required to execute the statement.
82		privs, err := stmt.RequiredPrivileges()
83		if err != nil {
84			return err
85		}
86
87		// Make sure the user has the privileges required to execute
88		// each statement.
89		for _, p := range privs {
90			if p.Admin {
91				// Admin privilege already checked so statement requiring admin
92				// privilege cannot be run.
93				return &ErrAuthorize{
94					Query:    query,
95					User:     u.Name,
96					Database: database,
97					Message:  fmt.Sprintf("statement '%s', requires admin privilege", stmt),
98				}
99			}
100
101			// Use the db name specified by the statement or the db
102			// name passed by the caller if one wasn't specified by
103			// the statement.
104			db := p.Name
105			if db == "" {
106				db = database
107			}
108			if !u.AuthorizeDatabase(p.Privilege, db) {
109				return &ErrAuthorize{
110					Query:    query,
111					User:     u.Name,
112					Database: database,
113					Message:  fmt.Sprintf("statement '%s', requires %s on %s", stmt, p.Privilege.String(), db),
114				}
115			}
116		}
117	}
118	return nil
119}
120
121// ErrAuthorize represents an authorization error.
122type ErrAuthorize struct {
123	Query    *influxql.Query
124	User     string
125	Database string
126	Message  string
127}
128
129// Error returns the text of the error.
130func (e ErrAuthorize) Error() string {
131	if e.User == "" {
132		return fmt.Sprint(e.Message)
133	}
134	return fmt.Sprintf("%s not authorized to execute %s", e.User, e.Message)
135}
136