diff --git a/callback.go b/callback.go index 5a735c0..2c68973 100644 --- a/callback.go +++ b/callback.go @@ -77,6 +77,12 @@ func updateHookTrampoline(handle uintptr, op int, db *C.char, table *C.char, row callback(op, C.GoString(db), C.GoString(table), rowid) } +//export authorizerTrampoline +func authorizerTrampoline(handle uintptr, op int, arg1 *C.char, arg2 *C.char, arg3 *C.char) int { + callback := lookupHandle(handle).(func(int, string, string, string) int) + return callback(op, C.GoString(arg1), C.GoString(arg2), C.GoString(arg3)) +} + // Use handles to avoid passing Go pointers to C. type handleVal struct { diff --git a/sqlite3.go b/sqlite3.go index d8c5b8d..ee68d21 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -119,6 +119,8 @@ int commitHookTrampoline(void*); void rollbackHookTrampoline(void*); void updateHookTrampoline(void*, int, char*, char*, sqlite3_int64); +int authorizerTrampoline(void*, int, char*, char*, char*, char*); + #ifdef SQLITE_LIMIT_WORKER_THREADS # define _SQLITE_HAS_LIMIT # define SQLITE_LIMIT_LENGTH 0 @@ -200,9 +202,48 @@ func Version() (libVersion string, libVersionNumber int, sourceID string) { } const ( + // used by authorizer and pre_update_hook SQLITE_DELETE = C.SQLITE_DELETE SQLITE_INSERT = C.SQLITE_INSERT SQLITE_UPDATE = C.SQLITE_UPDATE + + // used by authorzier - as return value + SQLITE_OK = C.SQLITE_OK + SQLITE_IGNORE = C.SQLITE_IGNORE + SQLITE_DENY = C.SQLITE_DENY + + // different actions query tries to do - passed as argument to authorizer + SQLITE_CREATE_INDEX = C.SQLITE_CREATE_INDEX + SQLITE_CREATE_TABLE = C.SQLITE_CREATE_TABLE + SQLITE_CREATE_TEMP_INDEX = C.SQLITE_CREATE_TEMP_INDEX + SQLITE_CREATE_TEMP_TABLE = C.SQLITE_CREATE_TEMP_TABLE + SQLITE_CREATE_TEMP_TRIGGER = C.SQLITE_CREATE_TEMP_TRIGGER + SQLITE_CREATE_TEMP_VIEW = C.SQLITE_CREATE_TEMP_VIEW + SQLITE_CREATE_TRIGGER = C.SQLITE_CREATE_TRIGGER + SQLITE_CREATE_VIEW = C.SQLITE_CREATE_VIEW + SQLITE_CREATE_VTABLE = C.SQLITE_CREATE_VTABLE + SQLITE_DROP_INDEX = C.SQLITE_DROP_INDEX + SQLITE_DROP_TABLE = C.SQLITE_DROP_TABLE + SQLITE_DROP_TEMP_INDEX = C.SQLITE_DROP_TEMP_INDEX + SQLITE_DROP_TEMP_TABLE = C.SQLITE_DROP_TEMP_TABLE + SQLITE_DROP_TEMP_TRIGGER = C.SQLITE_DROP_TEMP_TRIGGER + SQLITE_DROP_TEMP_VIEW = C.SQLITE_DROP_TEMP_VIEW + SQLITE_DROP_TRIGGER = C.SQLITE_DROP_TRIGGER + SQLITE_DROP_VIEW = C.SQLITE_DROP_VIEW + SQLITE_DROP_VTABLE = C.SQLITE_DROP_VTABLE + SQLITE_PRAGMA = C.SQLITE_PRAGMA + SQLITE_READ = C.SQLITE_READ + SQLITE_SELECT = C.SQLITE_SELECT + SQLITE_TRANSACTION = C.SQLITE_TRANSACTION + SQLITE_ATTACH = C.SQLITE_ATTACH + SQLITE_DETACH = C.SQLITE_DETACH + SQLITE_ALTER_TABLE = C.SQLITE_ALTER_TABLE + SQLITE_REINDEX = C.SQLITE_REINDEX + SQLITE_ANALYZE = C.SQLITE_ANALYZE + SQLITE_FUNCTION = C.SQLITE_FUNCTION + SQLITE_SAVEPOINT = C.SQLITE_SAVEPOINT + SQLITE_COPY = C.SQLITE_COPY + SQLITE_RECURSIVE = C.SQLITE_RECURSIVE ) // SQLiteDriver implements driver.Driver. @@ -440,6 +481,20 @@ func (c *SQLiteConn) RegisterUpdateHook(callback func(int, string, string, int64 } } +// RegisterAuthorizer sets the authorizer for connection. +// +// The parameters to the callback are the operation (one of the constants +// SQLITE_INSERT, SQLITE_DELETE, or SQLITE_UPDATE), and 1 to 3 arguments, +// depending on operation. More details see: +// https://www.sqlite.org/c3ref/c_alter_table.html +func (c *SQLiteConn) RegisterAuthorizer(callback func(int, string, string, string) int) { + if callback == nil { + C.sqlite3_set_authorizer(c.db, nil, nil) + } else { + C.sqlite3_set_authorizer(c.db, (*[0]byte)(C.authorizerTrampoline), unsafe.Pointer(newHandle(c, callback))) + } +} + // RegisterFunc makes a Go function available as a SQLite function. // // The Go function can have arguments of the following types: any diff --git a/sqlite3_test.go b/sqlite3_test.go index 75d8f52..bfed027 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -1574,6 +1574,47 @@ func TestUpdateAndTransactionHooks(t *testing.T) { } } +func TestAuthorizer(t *testing.T) { + var authorizerReturn = 0 + + sql.Register("sqlite3_Authorizer", &SQLiteDriver{ + ConnectHook: func(conn *SQLiteConn) error { + conn.RegisterAuthorizer(func(op int, arg1, arg2, arg3 string) int { + return authorizerReturn + }) + return nil + }, + }) + db, err := sql.Open("sqlite3_Authorizer", ":memory:") + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer db.Close() + + statements := []string{ + "create table foo (id integer primary key, name varchar)", + "insert into foo values (9, 'test9')", + "update foo set name = 'test99' where id = 9", + "select * from foo", + } + + authorizerReturn = SQLITE_OK + for _, statement := range statements { + _, err = db.Exec(statement) + if err != nil { + t.Fatalf("No error expected [%v]: %v", statement, err) + } + } + + authorizerReturn = SQLITE_DENY + for _, statement := range statements { + _, err = db.Exec(statement) + if err == nil { + t.Fatalf("Authorizer didn't worked - nil received, but error expected: [%v]", statement) + } + } +} + func TestNilAndEmptyBytes(t *testing.T) { db, err := sql.Open("sqlite3", ":memory:") if err != nil {