# 2013 May 8
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
# This file contains tests for the sqlite4_authorizer_push() and
# sqlite4_authorizer_pop() API functions.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/malloc_common.tcl
set testprefix auth4
ifcapable !auth { finish_test ; return }
#--------------------------------------------------------------------
# Test cases auth4-1.* test that when there are multiple authorizers
# on the stack, they are invoked in order from most to least recently
# added until all have been invoked or one of them returns other than
# SQLITE4_OK.
#
do_execsql_test 1.0 {
CREATE TABLE t1(x, y);
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
}
proc auth_callback {id code z1 z2 z3 z4} {
if {$code == "SQLITE4_READ" && $z1=="t1" && $z2=="y"} {
incr ::NAUTH
return [lindex $::AUTH $id]
}
return SQLITE4_OK
}
sqlite4_authorizer_push db {auth_callback 2}
sqlite4_authorizer_push db {auth_callback 1}
sqlite4_authorizer_push db {auth_callback 0}
foreach {tn codes ncall res} {
1 {SQLITE4_OK SQLITE4_OK SQLITE4_OK} 3 {0 {1 one 2 two}}
2 {SQLITE4_OK SQLITE4_OK SQLITE4_DENY} 3 {1 {access to t1.y is prohibited}}
3 {SQLITE4_DENY SQLITE4_OK SQLITE4_OK} 1 {1 {access to t1.y is prohibited}}
4 {SQLITE4_OK SQLITE4_DENY SQLITE4_OK} 2 {1 {access to t1.y is prohibited}}
5 {SQLITE4_OK SQLITE4_OK SQLITE4_IGNORE} 3 {0 {1 {} 2 {}}}
6 {SQLITE4_IGNORE SQLITE4_OK SQLITE4_OK} 1 {0 {1 {} 2 {}}}
7 {SQLITE4_OK SQLITE4_IGNORE SQLITE4_OK} 2 {0 {1 {} 2 {}}}
8 {SQLITE4_OK SQLITE4_OK SQLITE4_ALLOW} 3 {0 {1 one 2 two}}
9 {SQLITE4_ALLOW SQLITE4_OK SQLITE4_OK} 1 {0 {1 one 2 two}}
10 {SQLITE4_OK SQLITE4_ALLOW SQLITE4_OK} 2 {0 {1 one 2 two}}
} {
db cache flush
set ::AUTH $codes
set ::NAUTH 0
do_catchsql_test 1.$tn.1 { SELECT * FROM t1; } $res
do_test 1.$tn.2 { set ::NAUTH } $ncall
}
sqlite4_authorizer_pop db
sqlite4_authorizer_pop db
sqlite4_authorizer_pop db
#--------------------------------------------------------------------
# Test cases auth4-2.* test that the push and pop operations seem to
# work correctly.
#
set ::STACK [list]
proc auth_callback {id code z1 z2 z3 z4} {
if {$code == "SQLITE4_READ" && $z1=="t1" && $z2=="y"} {
lappend ::AUTH $id
}
return SQLITE4_OK
}
proc test_stack {} {
set ::AUTH [list]
db eval { SELECT * FROM t1 }
set ::AUTH
}
proc push {id} {
set ::STACK [concat $id $::STACK]
sqlite4_authorizer_push db [list auth_callback $id]
}
proc pop {} {
set ::STACK [lrange $::STACK 1 end]
catch { sqlite4_authorizer_pop db }
}
do_execsql_test 2.0 {
DROP TABLE IF EXISTS t1;
CREATE TABLE t1(x, y);
INSERT INTO t1 VALUES(1, 'one');
}
for {set i 1} {$i <= 100} {incr i} {
if { int(rand()*2.0) } {
pop
} else {
push [expr int(rand() * 500.0)]
}
do_test 2.$i { test_stack } $::STACK
}
#--------------------------------------------------------------------
# Test that sqlite4_authorizer_pop() returns an error if the stack is
# empty when it is called.
#
reset_db
do_test 3.1 {
list [catch { sqlite4_authorizer_pop db } msg] $msg
} {1 SQLITE4_ERROR}
do_test 3.2 {
sqlite4_authorizer_push db xyz
sqlite4_authorizer_pop db
} {}
#--------------------------------------------------------------------
# Test that authorizer destructors are invoked under the following
# circumstances:
#
# 1. When an authorizer is popped from the stack,
# 2. When the database connection is closed, and
# 3. When an error occurs within the sqlite4_authorizer_push() call.
#
reset_db
do_test 4.1.1 {
set ::xyz_destroyed 0
sqlite4_authorizer_push db xyz {incr ::xyz_destroyed}
set ::xyz_destroyed
} {0}
do_test 4.1.2 {
sqlite4_authorizer_pop db
set ::xyz_destroyed
} {1}
do_test 4.1.3 {
set ::abc_destroyed 0
set ::xyz_destroyed 0
sqlite4_authorizer_push db abc {incr ::abc_destroyed}
sqlite4_authorizer_push db xyz {incr ::xyz_destroyed}
list $::abc_destroyed $::xyz_destroyed
} {0 0}
do_test 4.1.4 {
sqlite4_authorizer_pop db
list $::abc_destroyed $::xyz_destroyed
} {0 1}
do_test 4.1.5 {
sqlite4_authorizer_pop db
list $::abc_destroyed $::xyz_destroyed
} {1 1}
do_test 4.1.6 {
db close
list $::abc_destroyed $::xyz_destroyed
} {1 1}
reset_db
do_test 4.2.1 {
set ::abc_destroyed 0
set ::xyz_destroyed 0
sqlite4_authorizer_push db abc {incr ::abc_destroyed}
sqlite4_authorizer_push db xyz {incr ::xyz_destroyed}
list $::abc_destroyed $::xyz_destroyed
} {0 0}
do_test 4.2.2 {
db close
list $::abc_destroyed $::xyz_destroyed
} {1 1}
# Normally we don't like to mix [do_faultsim_test] and friends with
# ordinary (non fault-injection) tests. But this one is OK because
# it runs very fast - sqlite4_authorizer_push() only calls malloc() once.
#
do_faultsim_test 4.3 -faults oom* -prep {
reset_db
set ::abc_destroyed 0
} -body {
sqlite4_authorizer_push db abc {incr ::abc_destroyed}
} -test {
faultsim_test_result {0 {}} {1 SQLITE4_NOMEM}
if {$testrc && $::abc_destroyed!=1} {error "destructor not invoked"}
if {$testrc==0 && $::abc_destroyed!=0} {error "destructor was invoked"}
}
finish_test