Files
2026-07-13 12:32:25 +08:00

392 lines
11 KiB
Go

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