status-go/vendor/zombiezen.com/go/sqlite/auth.go

319 lines
9.1 KiB
Go
Raw Permalink Normal View History

2022-03-10 09:44:48 +00:00
// Copyright 2021 Ross Light
// SPDX-License-Identifier: ISC
package sqlite
import (
"fmt"
"strings"
"sync"
"unsafe"
"modernc.org/libc"
lib "modernc.org/sqlite/lib"
)
// An Authorizer is called during statement preparation to see whether an action
// is allowed by the application. An Authorizer must not modify the database
// connection, including by preparing statements.
//
// See https://sqlite.org/c3ref/set_authorizer.html for a longer explanation.
type Authorizer interface {
Authorize(Action) AuthResult
}
// SetAuthorizer registers an authorizer for the database connection.
// SetAuthorizer(nil) clears any authorizer previously set.
func (c *Conn) SetAuthorizer(auth Authorizer) error {
if c == nil {
return fmt.Errorf("sqlite: set authorizer: nil connection")
}
if auth == nil {
c.releaseAuthorizer()
res := ResultCode(lib.Xsqlite3_set_authorizer(c.tls, c.conn, 0, 0))
if err := reserr(res); err != nil {
return fmt.Errorf("sqlite: set authorizer: %w", err)
}
return nil
}
authorizers.mu.Lock()
if authorizers.m == nil {
authorizers.m = make(map[uintptr]Authorizer)
}
authorizers.m[c.conn] = auth
authorizers.mu.Unlock()
// The following is a conversion from function value to uintptr. It assumes
// the memory representation described in https://golang.org/s/go11func.
//
// It does this by doing the following in order:
// 1) Create a Go struct containing a pointer to a pointer to
// authTrampoline. It is assumed that the pointer to authTrampoline will be
// stored in the read-only data section and thus will not move.
// 2) Convert the pointer to the Go struct to a pointer to uintptr through
// unsafe.Pointer. This is permitted via Rule #1 of unsafe.Pointer.
// 3) Dereference the pointer to uintptr to obtain the function value as a
// uintptr. This is safe as long as function values are passed as pointers.
xAuth := *(*uintptr)(unsafe.Pointer(&struct {
f func(*libc.TLS, uintptr, int32, uintptr, uintptr, uintptr, uintptr) int32
}{authTrampoline}))
res := ResultCode(lib.Xsqlite3_set_authorizer(c.tls, c.conn, xAuth, c.conn))
if err := reserr(res); err != nil {
return fmt.Errorf("sqlite: set authorizer: %w", err)
}
return nil
}
func (c *Conn) releaseAuthorizer() {
authorizers.mu.Lock()
delete(authorizers.m, c.conn)
authorizers.mu.Unlock()
}
var authorizers struct {
mu sync.RWMutex
m map[uintptr]Authorizer // sqlite3* -> Authorizer
}
func authTrampoline(tls *libc.TLS, conn uintptr, op int32, cArg1, cArg2, cDB, cTrigger uintptr) int32 {
authorizers.mu.RLock()
auth := authorizers.m[conn]
authorizers.mu.RUnlock()
return int32(auth.Authorize(Action{
op: OpType(op),
arg1: libc.GoString(cArg1),
arg2: libc.GoString(cArg2),
database: libc.GoString(cDB),
trigger: libc.GoString(cTrigger),
}))
}
// AuthorizeFunc is a function that implements Authorizer.
type AuthorizeFunc func(Action) AuthResult
// Authorize calls f.
func (f AuthorizeFunc) Authorize(action Action) AuthResult {
return f(action)
}
// AuthResult is the result of a call to an Authorizer. The zero value is
// AuthResultOK.
type AuthResult int32
// Possible return values from Authorize.
const (
// AuthResultOK allows the SQL statement to be compiled.
AuthResultOK AuthResult = lib.SQLITE_OK
// AuthResultDeny causes the entire SQL statement to be rejected with an error.
AuthResultDeny AuthResult = lib.SQLITE_DENY
// AuthResultIgnore disallows the specific action but allow the SQL statement
// to continue to be compiled. For OpRead, this substitutes a NULL for the
// column value. For OpDelete, the DELETE operation proceeds but the truncate
// optimization is disabled and all rows are deleted individually.
AuthResultIgnore AuthResult = lib.SQLITE_IGNORE
)
// String returns the C constant name of the result.
func (result AuthResult) String() string {
switch result {
case AuthResultOK:
return "SQLITE_OK"
case AuthResultDeny:
return "SQLITE_DENY"
case AuthResultIgnore:
return "SQLITE_IGNORE"
default:
return fmt.Sprintf("AuthResult(%d)", int32(result))
}
}
// Action represents an action to be authorized.
type Action struct {
op OpType
arg1 string
arg2 string
database string
trigger string
}
// Mapping of argument position to concept at:
// https://sqlite.org/c3ref/c_alter_table.html
// Type returns the type of action being authorized.
func (action Action) Type() OpType {
return action.op
}
// Accessor returns the name of the inner-most trigger or view that is
// responsible for the access attempt or the empty string if this access attempt
// is directly from top-level SQL code.
func (action Action) Accessor() string {
return action.trigger
}
// Database returns the name of the database (e.g. "main", "temp", etc.) this
// action affects or the empty string if not applicable.
func (action Action) Database() string {
switch action.op {
case OpDetach, OpAlterTable:
return action.arg1
default:
return action.database
}
}
// Index returns the name of the index this action affects or the empty string
// if not applicable.
func (action Action) Index() string {
switch action.op {
case OpCreateIndex, OpCreateTempIndex, OpDropIndex, OpDropTempIndex, OpReindex:
return action.arg1
default:
return ""
}
}
// Table returns the name of the table this action affects or the empty string
// if not applicable.
func (action Action) Table() string {
switch action.op {
case OpCreateTable, OpCreateTempTable, OpDelete, OpDropTable, OpDropTempTable, OpInsert, OpRead, OpUpdate, OpAnalyze, OpCreateVTable, OpDropVTable:
return action.arg1
case OpCreateIndex, OpCreateTempIndex, OpCreateTempTrigger, OpCreateTrigger, OpDropIndex, OpDropTempIndex, OpDropTempTrigger, OpDropTrigger, OpAlterTable:
return action.arg2
default:
return ""
}
}
// Trigger returns the name of the trigger this action affects or the empty
// string if not applicable.
func (action Action) Trigger() string {
switch action.op {
case OpCreateTempTrigger, OpCreateTrigger, OpDropTempTrigger, OpDropTrigger:
return action.arg1
default:
return ""
}
}
// View returns the name of the view this action affects or the empty string
// if not applicable.
func (action Action) View() string {
switch action.op {
case OpCreateTempView, OpCreateView, OpDropTempView, OpDropView:
return action.arg1
default:
return ""
}
}
// Pragma returns the name of the action's PRAGMA command or the empty string
// if the action does not represent a PRAGMA command.
// See https://sqlite.org/pragma.html#toc for a list of possible values.
func (action Action) Pragma() string {
if action.op != OpPragma {
return ""
}
return action.arg1
}
// PragmaArg returns the argument to the PRAGMA command or the empty string if
// the action does not represent a PRAGMA command or the PRAGMA command does not
// take an argument.
func (action Action) PragmaArg() string {
if action.op != OpPragma {
return ""
}
return action.arg2
}
// Column returns the name of the column this action affects or the empty string
// if not applicable. For OpRead actions, this will return the empty string if a
// table is referenced but no column values are extracted from that table
// (e.g. a query like "SELECT COUNT(*) FROM tab").
func (action Action) Column() string {
switch action.op {
case OpRead, OpUpdate:
return action.arg2
default:
return ""
}
}
// Operation returns one of "BEGIN", "COMMIT", "RELEASE", or "ROLLBACK" for a
// transaction or savepoint statement or the empty string otherwise.
func (action Action) Operation() string {
switch action.op {
case OpTransaction, OpSavepoint:
return action.arg1
default:
return ""
}
}
// File returns the name of the file being ATTACHed or the empty string if the
// action does not represent an ATTACH DATABASE statement.
func (action Action) File() string {
if action.op != OpAttach {
return ""
}
return action.arg1
}
// Module returns the module name given to the virtual table statement or the
// empty string if the action does not represent a CREATE VIRTUAL TABLE or
// DROP VIRTUAL TABLE statement.
func (action Action) Module() string {
switch action.op {
case OpCreateVTable, OpDropVTable:
return action.arg2
default:
return ""
}
}
// Savepoint returns the name given to the SAVEPOINT statement or the empty
// string if the action does not represent a SAVEPOINT statement.
func (action Action) Savepoint() string {
if action.op != OpSavepoint {
return ""
}
return action.arg2
}
// String returns a debugging representation of the action.
func (action Action) String() string {
sb := new(strings.Builder)
sb.WriteString(action.op.String())
params := []struct {
name, value string
}{
{"database", action.Database()},
{"file", action.File()},
{"trigger", action.Trigger()},
{"index", action.Index()},
{"table", action.Table()},
{"view", action.View()},
{"module", action.Module()},
{"column", action.Column()},
{"operation", action.Operation()},
{"savepoint", action.Savepoint()},
{"pragma", action.Pragma()},
{"arg", action.PragmaArg()},
}
for _, p := range params {
if p.value != "" {
sb.WriteString(" ")
sb.WriteString(p.name)
sb.WriteString(":")
sb.WriteString(p.value)
}
}
return sb.String()
}