394 lines
12 KiB
Go
394 lines
12 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"
|
|
)
|
|
|
|
// Revoke handles all of the REVOKE statements.
|
|
type Revoke struct {
|
|
RevokeTable *RevokeTable
|
|
RevokeSchema *RevokeSchema
|
|
RevokeDatabase *RevokeDatabase
|
|
RevokeSequence *RevokeSequence
|
|
RevokeRoutine *RevokeRoutine
|
|
RevokeRole *RevokeRole
|
|
FromRoles []string
|
|
GrantedBy string
|
|
GrantOptionFor bool // This is "ADMIN OPTION FOR" for RevokeRole only
|
|
Cascade bool // When false, represents RESTRICT
|
|
}
|
|
|
|
// RevokeTable specifically handles the REVOKE ... ON TABLE statement.
|
|
type RevokeTable struct {
|
|
Privileges []auth.Privilege
|
|
Tables []doltdb.TableName
|
|
}
|
|
|
|
// RevokeSchema specifically handles the REVOKE ... ON SCHEMA statement.
|
|
type RevokeSchema struct {
|
|
Privileges []auth.Privilege
|
|
Schemas []string
|
|
}
|
|
|
|
// RevokeDatabase specifically handles the REVOKE ... ON DATABASE statement.
|
|
type RevokeDatabase struct {
|
|
Privileges []auth.Privilege
|
|
Databases []string
|
|
}
|
|
|
|
// RevokeSequence specifically handles the REVOKE ... ON SEQUENCE statement.
|
|
type RevokeSequence struct {
|
|
Privileges []auth.Privilege
|
|
Sequences []auth.SequencePrivilegeKey
|
|
}
|
|
|
|
// RevokeRoutine specifically handles the REVOKE ... ON FUNCTION/PROCEDURE/ROUTINE statement.
|
|
type RevokeRoutine struct {
|
|
Privileges []auth.Privilege
|
|
Routines []auth.RoutinePrivilegeKey
|
|
}
|
|
|
|
// RevokeRole specifically handles the REVOKE <roles> FROM <roles> statement.
|
|
type RevokeRole struct {
|
|
Groups []string
|
|
}
|
|
|
|
var _ sql.ExecSourceRel = (*Revoke)(nil)
|
|
var _ vitess.Injectable = (*Revoke)(nil)
|
|
|
|
// Children implements the interface sql.ExecSourceRel.
|
|
func (r *Revoke) Children() []sql.Node {
|
|
return nil
|
|
}
|
|
|
|
// IsReadOnly implements the interface sql.ExecSourceRel.
|
|
func (r *Revoke) IsReadOnly() bool {
|
|
return false
|
|
}
|
|
|
|
// Resolved implements the interface sql.ExecSourceRel.
|
|
func (r *Revoke) Resolved() bool {
|
|
return true
|
|
}
|
|
|
|
// RowIter implements the interface sql.ExecSourceRel.
|
|
func (r *Revoke) RowIter(ctx *sql.Context, _ sql.Row) (sql.RowIter, error) {
|
|
if r.Cascade {
|
|
return nil, errors.New("REVOKE does not yet support CASCADE")
|
|
}
|
|
|
|
var err error
|
|
auth.LockWrite(func() {
|
|
switch {
|
|
case r.RevokeTable != nil:
|
|
if err = r.revokeTable(ctx); err != nil {
|
|
return
|
|
}
|
|
case r.RevokeSchema != nil:
|
|
if err = r.revokeSchema(ctx); err != nil {
|
|
return
|
|
}
|
|
case r.RevokeDatabase != nil:
|
|
if err = r.revokeDatabase(ctx); err != nil {
|
|
return
|
|
}
|
|
case r.RevokeSequence != nil:
|
|
if err = r.revokeSequence(ctx); err != nil {
|
|
return
|
|
}
|
|
case r.RevokeRoutine != nil:
|
|
if err = r.revokeRoutine(ctx); err != nil {
|
|
return
|
|
}
|
|
case r.RevokeRole != nil:
|
|
if err = r.revokeRole(ctx); err != nil {
|
|
return
|
|
}
|
|
default:
|
|
err = errors.Errorf("REVOKE 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 (r *Revoke) Schema(ctx *sql.Context) sql.Schema {
|
|
return nil
|
|
}
|
|
|
|
// String implements the interface sql.ExecSourceRel.
|
|
func (r *Revoke) String() string {
|
|
switch {
|
|
case r.RevokeTable != nil:
|
|
return "REVOKE TABLE"
|
|
default:
|
|
return "REVOKE"
|
|
}
|
|
}
|
|
|
|
// WithChildren implements the interface sql.ExecSourceRel.
|
|
func (r *Revoke) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
|
|
return plan.NillaryWithChildren(r, children...)
|
|
}
|
|
|
|
// WithResolvedChildren implements the interface vitess.Injectable.
|
|
func (r *Revoke) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
|
|
if len(children) != 0 {
|
|
return nil, ErrVitessChildCount.New(0, len(children))
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// common handles the initial logic for each REVOKE statement. `roles` are the `FromRoles`. `userRole` is the role of
|
|
// the session's selected user. `grantedByID` is the `GrantedBy` user if specified (or `userRole` if not).
|
|
func (r *Revoke) common(ctx *sql.Context) (roles []auth.Role, userRole auth.Role, grantedByID auth.RoleID, err error) {
|
|
roles = make([]auth.Role, len(r.FromRoles))
|
|
// First we'll verify that all of the roles exist
|
|
for i, roleName := range r.FromRoles {
|
|
roles[i] = auth.GetRole(roleName)
|
|
if !roles[i].IsValid() {
|
|
return nil, auth.Role{}, 0, errors.Errorf(`role "%s" does not exist`, roleName)
|
|
}
|
|
}
|
|
// Then we'll check that the role that is revoking the privileges exists
|
|
userRole = auth.GetRole(ctx.Client().User)
|
|
if !userRole.IsValid() {
|
|
return nil, auth.Role{}, 0, errors.Errorf(`role "%s" does not exist`, ctx.Client().User)
|
|
}
|
|
if len(r.GrantedBy) != 0 {
|
|
grantedByRole := auth.GetRole(r.GrantedBy)
|
|
if !grantedByRole.IsValid() {
|
|
return nil, auth.Role{}, 0, errors.Errorf(`role "%s" does not exist`, r.GrantedBy)
|
|
}
|
|
if groupID, _, _ := auth.IsRoleAMember(userRole.ID(), grantedByRole.ID()); !groupID.IsValid() {
|
|
// TODO: grab the actual error message
|
|
return nil, auth.Role{}, 0, errors.Errorf(`role "%s" does not have permission to revoke this privilege`, userRole.Name)
|
|
}
|
|
} else {
|
|
grantedByID = userRole.ID()
|
|
}
|
|
return roles, userRole, grantedByID, nil
|
|
}
|
|
|
|
// revokeTable handles *RevokeTable from within RowIter.
|
|
func (r *Revoke) revokeTable(ctx *sql.Context) error {
|
|
roles, userRole, grantedByID, err := r.common(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, role := range roles {
|
|
for _, table := range r.RevokeTable.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 r.RevokeTable.Privileges {
|
|
if id := auth.HasTablePrivilegeGrantOption(key, privilege); !id.IsValid() {
|
|
// TODO: grab the actual error message
|
|
return errors.Errorf(`role "%s" does not have permission to revoke this privilege`, userRole.Name)
|
|
}
|
|
auth.RemoveTablePrivilege(auth.TablePrivilegeKey{
|
|
Role: role.ID(),
|
|
Table: doltdb.TableName{Name: table.Name, Schema: schemaName},
|
|
}, auth.GrantedPrivilege{
|
|
Privilege: privilege,
|
|
GrantedBy: grantedByID,
|
|
}, r.GrantOptionFor)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// revokeSchema handles *RevokeSchema from within RowIter.
|
|
func (r *Revoke) revokeSchema(ctx *sql.Context) error {
|
|
roles, userRole, grantedByID, err := r.common(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, role := range roles {
|
|
for _, schema := range r.RevokeSchema.Schemas {
|
|
key := auth.SchemaPrivilegeKey{
|
|
Role: userRole.ID(),
|
|
Schema: schema,
|
|
}
|
|
for _, privilege := range r.RevokeSchema.Privileges {
|
|
if id := auth.HasSchemaPrivilegeGrantOption(key, privilege); !id.IsValid() {
|
|
// TODO: grab the actual error message
|
|
return errors.Errorf(`role "%s" does not have permission to revoke this privilege`, userRole.Name)
|
|
}
|
|
auth.RemoveSchemaPrivilege(auth.SchemaPrivilegeKey{
|
|
Role: role.ID(),
|
|
Schema: schema,
|
|
}, auth.GrantedPrivilege{
|
|
Privilege: privilege,
|
|
GrantedBy: grantedByID,
|
|
}, r.GrantOptionFor)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// revokeDatabase handles *RevokeDatabase from within RowIter.
|
|
func (r *Revoke) revokeDatabase(ctx *sql.Context) error {
|
|
roles, userRole, grantedByID, err := r.common(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, role := range roles {
|
|
for _, databases := range r.RevokeDatabase.Databases {
|
|
key := auth.DatabasePrivilegeKey{
|
|
Role: userRole.ID(),
|
|
Name: databases,
|
|
}
|
|
for _, privilege := range r.RevokeDatabase.Privileges {
|
|
if id := auth.HasDatabasePrivilegeGrantOption(key, privilege); !id.IsValid() {
|
|
// TODO: grab the actual error message
|
|
return errors.Errorf(`role "%s" does not have permission to revoke this privilege`, userRole.Name)
|
|
}
|
|
auth.RemoveDatabasePrivilege(auth.DatabasePrivilegeKey{
|
|
Role: role.ID(),
|
|
Name: databases,
|
|
}, auth.GrantedPrivilege{
|
|
Privilege: privilege,
|
|
GrantedBy: grantedByID,
|
|
}, r.GrantOptionFor)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// revokeSequence handles *RevokeSequence from within RowIter.
|
|
func (r *Revoke) revokeSequence(ctx *sql.Context) error {
|
|
roles, userRole, grantedByID, err := r.common(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, role := range roles {
|
|
for _, seq := range r.RevokeSequence.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 r.RevokeSequence.Privileges {
|
|
if id := auth.HasSequencePrivilegeGrantOption(key, privilege); !id.IsValid() {
|
|
// TODO: grab the actual error message
|
|
return errors.Errorf(`role "%s" does not have permission to revoke this privilege`, userRole.Name)
|
|
}
|
|
auth.RemoveSequencePrivilege(auth.SequencePrivilegeKey{
|
|
Role: role.ID(),
|
|
Schema: schemaName,
|
|
Name: seq.Name,
|
|
}, auth.GrantedPrivilege{
|
|
Privilege: privilege,
|
|
GrantedBy: grantedByID,
|
|
}, r.GrantOptionFor)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// revokeRoutine handles *RevokeRoutine from within RowIter.
|
|
func (r *Revoke) revokeRoutine(ctx *sql.Context) error {
|
|
roles, userRole, grantedByID, err := r.common(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, role := range roles {
|
|
for _, routine := range r.RevokeRoutine.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 r.RevokeRoutine.Privileges {
|
|
if id := auth.HasRoutinePrivilegeGrantOption(key, privilege); !id.IsValid() {
|
|
// TODO: grab the actual error message
|
|
return errors.Errorf(`role "%s" does not have permission to revoke this privilege`, userRole.Name)
|
|
}
|
|
auth.RemoveRoutinePrivilege(auth.RoutinePrivilegeKey{
|
|
Role: role.ID(),
|
|
Schema: schemaName,
|
|
Name: routine.Name,
|
|
ArgTypes: routine.ArgTypes,
|
|
}, auth.GrantedPrivilege{
|
|
Privilege: privilege,
|
|
GrantedBy: grantedByID,
|
|
}, r.GrantOptionFor)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// revokeRole handles *RevokeRole from within RowIter.
|
|
func (r *Revoke) revokeRole(ctx *sql.Context) error {
|
|
members, userRole, _, err := r.common(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
groups := make([]auth.Role, len(r.RevokeRole.Groups))
|
|
for i, groupName := range r.RevokeRole.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 revoke role "%s"`, userRole.Name, group.Name)
|
|
}
|
|
auth.RemoveMemberFromGroup(member.ID(), group.ID(), r.GrantOptionFor)
|
|
}
|
|
}
|
|
return nil
|
|
}
|