chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
// Copyright 2024 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
|
||||
"github.com/dolthub/doltgresql/core"
|
||||
"github.com/dolthub/doltgresql/server/auth"
|
||||
)
|
||||
|
||||
// Grant handles all of the GRANT statements.
|
||||
type Grant struct {
|
||||
GrantTable *GrantTable
|
||||
GrantSchema *GrantSchema
|
||||
GrantDatabase *GrantDatabase
|
||||
GrantSequence *GrantSequence
|
||||
GrantRoutine *GrantRoutine
|
||||
GrantRole *GrantRole
|
||||
ToRoles []string
|
||||
WithGrantOption bool // This is "WITH ADMIN OPTION" for GrantRole only
|
||||
GrantedBy string
|
||||
}
|
||||
|
||||
// GrantTable specifically handles the GRANT ... ON TABLE statement.
|
||||
type GrantTable struct {
|
||||
Privileges []auth.Privilege
|
||||
Tables []doltdb.TableName
|
||||
}
|
||||
|
||||
// GrantSchema specifically handles the GRANT ... ON SCHEMA statement.
|
||||
type GrantSchema struct {
|
||||
Privileges []auth.Privilege
|
||||
Schemas []string
|
||||
}
|
||||
|
||||
// GrantDatabase specifically handles the GRANT ... ON DATABASE statement.
|
||||
type GrantDatabase struct {
|
||||
Privileges []auth.Privilege
|
||||
Databases []string
|
||||
}
|
||||
|
||||
// GrantSequence specifically handles the GRANT ... ON SEQUENCE statement.
|
||||
type GrantSequence struct {
|
||||
Privileges []auth.Privilege
|
||||
Sequences []auth.SequencePrivilegeKey
|
||||
}
|
||||
|
||||
// GrantRoutine specifically handles the GRANT ... ON FUNCTION/PROCEDURE/ROUTINE statement.
|
||||
type GrantRoutine struct {
|
||||
Privileges []auth.Privilege
|
||||
Routines []auth.RoutinePrivilegeKey
|
||||
}
|
||||
|
||||
// GrantRole specifically handles the GRANT <roles> TO <roles> statement.
|
||||
type GrantRole struct {
|
||||
Groups []string
|
||||
}
|
||||
|
||||
var _ sql.ExecSourceRel = (*Grant)(nil)
|
||||
var _ vitess.Injectable = (*Grant)(nil)
|
||||
|
||||
// Children implements the interface sql.ExecSourceRel.
|
||||
func (g *Grant) Children() []sql.Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsReadOnly implements the interface sql.ExecSourceRel.
|
||||
func (g *Grant) IsReadOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Resolved implements the interface sql.ExecSourceRel.
|
||||
func (g *Grant) Resolved() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// RowIter implements the interface sql.ExecSourceRel.
|
||||
func (g *Grant) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
|
||||
var err error
|
||||
auth.LockWrite(func() {
|
||||
switch {
|
||||
case g.GrantTable != nil:
|
||||
if err = g.grantTable(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
case g.GrantSchema != nil:
|
||||
if err = g.grantSchema(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
case g.GrantDatabase != nil:
|
||||
if err = g.grantDatabase(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
case g.GrantSequence != nil:
|
||||
if err = g.grantSequence(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
case g.GrantRoutine != nil:
|
||||
if err = g.grantRoutine(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
case g.GrantRole != nil:
|
||||
if err = g.grantRole(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = errors.Errorf("GRANT statement is not yet supported")
|
||||
return
|
||||
}
|
||||
err = auth.PersistChanges()
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sql.RowsToRowIter(), nil
|
||||
}
|
||||
|
||||
// Schema implements the interface sql.ExecSourceRel.
|
||||
func (g *Grant) Schema(ctx *sql.Context) sql.Schema {
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements the interface sql.ExecSourceRel.
|
||||
func (g *Grant) String() string {
|
||||
switch {
|
||||
case g.GrantTable != nil:
|
||||
return "GRANT TABLE"
|
||||
default:
|
||||
return "GRANT"
|
||||
}
|
||||
}
|
||||
|
||||
// WithChildren implements the interface sql.ExecSourceRel.
|
||||
func (g *Grant) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
|
||||
return plan.NillaryWithChildren(g, children...)
|
||||
}
|
||||
|
||||
// WithResolvedChildren implements the interface vitess.Injectable.
|
||||
func (g *Grant) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
|
||||
if len(children) != 0 {
|
||||
return nil, ErrVitessChildCount.New(0, len(children))
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// common handles the initial logic for each GRANT statement. `roles` are the `ToRoles`. `userRole` is the role of the
|
||||
// session's selected user.
|
||||
func (g *Grant) common(ctx *sql.Context) (roles []auth.Role, userRole auth.Role, err error) {
|
||||
roles = make([]auth.Role, len(g.ToRoles))
|
||||
// First we'll verify that all of the roles exist
|
||||
for i, roleName := range g.ToRoles {
|
||||
roles[i] = auth.GetRole(roleName)
|
||||
if !roles[i].IsValid() {
|
||||
return nil, auth.Role{}, errors.Errorf(`role "%s" does not exist`, roleName)
|
||||
}
|
||||
}
|
||||
// Then we'll check that the role that is granting the privileges exists
|
||||
userRole = auth.GetRole(ctx.Client().User)
|
||||
if !userRole.IsValid() {
|
||||
return nil, auth.Role{}, errors.Errorf(`role "%s" does not exist`, ctx.Client().User)
|
||||
}
|
||||
if len(g.GrantedBy) != 0 {
|
||||
grantedByRole := auth.GetRole(g.GrantedBy)
|
||||
if !grantedByRole.IsValid() {
|
||||
return nil, auth.Role{}, errors.Errorf(`role "%s" does not exist`, g.GrantedBy)
|
||||
}
|
||||
if userRole.ID() != grantedByRole.ID() {
|
||||
// TODO: grab the actual error message
|
||||
return nil, auth.Role{}, errors.New("GRANTED BY may only be set to the calling user")
|
||||
}
|
||||
}
|
||||
return roles, userRole, nil
|
||||
}
|
||||
|
||||
// grantTable handles *GrantTable from within RowIter.
|
||||
func (g *Grant) grantTable(ctx *sql.Context) error {
|
||||
roles, userRole, err := g.common(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, role := range roles {
|
||||
for _, table := range g.GrantTable.Tables {
|
||||
schemaName, err := core.GetSchemaName(ctx, nil, table.Schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := auth.TablePrivilegeKey{
|
||||
Role: userRole.ID(),
|
||||
Table: doltdb.TableName{Name: table.Name, Schema: schemaName},
|
||||
}
|
||||
for _, privilege := range g.GrantTable.Privileges {
|
||||
grantedBy := auth.HasTablePrivilegeGrantOption(key, privilege)
|
||||
if !grantedBy.IsValid() {
|
||||
// TODO: grab the actual error message
|
||||
return errors.Errorf(`role "%s" does not have permission to grant this privilege`, userRole.Name)
|
||||
}
|
||||
auth.AddTablePrivilege(auth.TablePrivilegeKey{
|
||||
Role: role.ID(),
|
||||
Table: doltdb.TableName{Name: table.Name, Schema: schemaName},
|
||||
}, auth.GrantedPrivilege{
|
||||
Privilege: privilege,
|
||||
GrantedBy: grantedBy,
|
||||
}, g.WithGrantOption)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// grantSchema handles *GrantSchema from within RowIter.
|
||||
func (g *Grant) grantSchema(ctx *sql.Context) error {
|
||||
roles, userRole, err := g.common(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, role := range roles {
|
||||
for _, schema := range g.GrantSchema.Schemas {
|
||||
key := auth.SchemaPrivilegeKey{
|
||||
Role: userRole.ID(),
|
||||
Schema: schema,
|
||||
}
|
||||
for _, privilege := range g.GrantSchema.Privileges {
|
||||
grantedBy := auth.HasSchemaPrivilegeGrantOption(key, privilege)
|
||||
if !grantedBy.IsValid() {
|
||||
// TODO: grab the actual error message
|
||||
return errors.Errorf(`role "%s" does not have permission to grant this privilege`, userRole.Name)
|
||||
}
|
||||
auth.AddSchemaPrivilege(auth.SchemaPrivilegeKey{
|
||||
Role: role.ID(),
|
||||
Schema: schema,
|
||||
}, auth.GrantedPrivilege{
|
||||
Privilege: privilege,
|
||||
GrantedBy: grantedBy,
|
||||
}, g.WithGrantOption)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// grantDatabase handles *GrantDatabase from within RowIter.
|
||||
func (g *Grant) grantDatabase(ctx *sql.Context) error {
|
||||
roles, userRole, err := g.common(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, role := range roles {
|
||||
for _, database := range g.GrantDatabase.Databases {
|
||||
key := auth.DatabasePrivilegeKey{
|
||||
Role: userRole.ID(),
|
||||
Name: database,
|
||||
}
|
||||
for _, privilege := range g.GrantDatabase.Privileges {
|
||||
grantedBy := auth.HasDatabasePrivilegeGrantOption(key, privilege)
|
||||
if !grantedBy.IsValid() {
|
||||
// TODO: grab the actual error message
|
||||
return errors.Errorf(`role "%s" does not have permission to grant this privilege`, userRole.Name)
|
||||
}
|
||||
auth.AddDatabasePrivilege(auth.DatabasePrivilegeKey{
|
||||
Role: role.ID(),
|
||||
Name: database,
|
||||
}, auth.GrantedPrivilege{
|
||||
Privilege: privilege,
|
||||
GrantedBy: grantedBy,
|
||||
}, g.WithGrantOption)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// grantSequence handles *GrantSequence from within RowIter.
|
||||
func (g *Grant) grantSequence(ctx *sql.Context) error {
|
||||
roles, userRole, err := g.common(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, role := range roles {
|
||||
for _, seq := range g.GrantSequence.Sequences {
|
||||
schemaName, err := core.GetSchemaName(ctx, nil, seq.Schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := auth.SequencePrivilegeKey{
|
||||
Role: userRole.ID(),
|
||||
Schema: schemaName,
|
||||
Name: seq.Name,
|
||||
}
|
||||
for _, privilege := range g.GrantSequence.Privileges {
|
||||
grantedBy := auth.HasSequencePrivilegeGrantOption(key, privilege)
|
||||
if !grantedBy.IsValid() {
|
||||
// TODO: grab the actual error message
|
||||
return errors.Errorf(`role "%s" does not have permission to grant this privilege`, userRole.Name)
|
||||
}
|
||||
auth.AddSequencePrivilege(auth.SequencePrivilegeKey{
|
||||
Role: role.ID(),
|
||||
Schema: schemaName,
|
||||
Name: seq.Name,
|
||||
}, auth.GrantedPrivilege{
|
||||
Privilege: privilege,
|
||||
GrantedBy: grantedBy,
|
||||
}, g.WithGrantOption)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// grantRoutine handles *GrantRoutine from within RowIter.
|
||||
func (g *Grant) grantRoutine(ctx *sql.Context) error {
|
||||
roles, userRole, err := g.common(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, role := range roles {
|
||||
for _, routine := range g.GrantRoutine.Routines {
|
||||
schemaName, err := core.GetSchemaName(ctx, nil, routine.Schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := auth.RoutinePrivilegeKey{
|
||||
Role: userRole.ID(),
|
||||
Schema: schemaName,
|
||||
Name: routine.Name,
|
||||
ArgTypes: routine.ArgTypes,
|
||||
}
|
||||
for _, privilege := range g.GrantRoutine.Privileges {
|
||||
grantedBy := auth.HasRoutinePrivilegeGrantOption(key, privilege)
|
||||
if !grantedBy.IsValid() {
|
||||
// TODO: grab the actual error message
|
||||
return errors.Errorf(`role "%s" does not have permission to grant this privilege`, userRole.Name)
|
||||
}
|
||||
auth.AddRoutinePrivilege(auth.RoutinePrivilegeKey{
|
||||
Role: role.ID(),
|
||||
Schema: schemaName,
|
||||
Name: routine.Name,
|
||||
ArgTypes: routine.ArgTypes,
|
||||
}, auth.GrantedPrivilege{
|
||||
Privilege: privilege,
|
||||
GrantedBy: grantedBy,
|
||||
}, g.WithGrantOption)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// grantRole handles *GrantRole from within RowIter.
|
||||
func (g *Grant) grantRole(ctx *sql.Context) error {
|
||||
members, userRole, err := g.common(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
groups := make([]auth.Role, len(g.GrantRole.Groups))
|
||||
for i, groupName := range g.GrantRole.Groups {
|
||||
groups[i] = auth.GetRole(groupName)
|
||||
if !groups[i].IsValid() {
|
||||
return errors.Errorf(`role "%s" does not exist`, groupName)
|
||||
}
|
||||
}
|
||||
for _, member := range members {
|
||||
for _, group := range groups {
|
||||
memberGroupID, _, withAdminOption := auth.IsRoleAMember(userRole.ID(), group.ID())
|
||||
if !memberGroupID.IsValid() || !withAdminOption {
|
||||
// TODO: grab the actual error message
|
||||
return errors.Errorf(`role "%s" does not have permission to grant role "%s"`, userRole.Name, group.Name)
|
||||
}
|
||||
auth.AddMemberToGroup(member.ID(), group.ID(), g.WithGrantOption, memberGroupID)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user