1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
|
// Copyright 2021 Roxy Light
// SPDX-License-Identifier: ISC
package sqlite
import (
"fmt"
"strings"
"sync"
"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 := res.ToError(); 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()
xAuth := cFuncPointer(authTrampoline)
res := ResultCode(lib.Xsqlite3_set_authorizer(c.tls, c.conn, xAuth, c.conn))
if err := res.ToError(); 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()
}
|