Skip to content

Commit cdf14c8

Browse files
committed
libsql: Add authorizer support
This adds support for `sqlite3_set_authorizer()`. The API follows rusqlite and the enumerations are lifted straight from rusqlite for compatibility.
1 parent 9a65514 commit cdf14c8

7 files changed

Lines changed: 446 additions & 3 deletions

File tree

libsql/src/auth.rs

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
use libsql_sys::ffi;
2+
3+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
4+
pub struct AuthContext<'a> {
5+
pub action: AuthAction<'a>,
6+
7+
pub database_name: Option<&'a str>,
8+
9+
pub accessor: Option<&'a str>,
10+
}
11+
12+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13+
pub enum AuthAction<'a> {
14+
Unknown {
15+
code: i32,
16+
arg1: Option<&'a str>,
17+
arg2: Option<&'a str>,
18+
},
19+
CreateIndex {
20+
index_name: &'a str,
21+
table_name: &'a str,
22+
},
23+
CreateTable {
24+
table_name: &'a str,
25+
},
26+
CreateTempIndex {
27+
index_name: &'a str,
28+
table_name: &'a str,
29+
},
30+
CreateTempTable {
31+
table_name: &'a str,
32+
},
33+
CreateTempTrigger {
34+
trigger_name: &'a str,
35+
table_name: &'a str,
36+
},
37+
CreateTempView {
38+
view_name: &'a str,
39+
},
40+
CreateTrigger {
41+
trigger_name: &'a str,
42+
table_name: &'a str,
43+
},
44+
CreateView {
45+
view_name: &'a str,
46+
},
47+
Delete {
48+
table_name: &'a str,
49+
},
50+
DropIndex {
51+
index_name: &'a str,
52+
table_name: &'a str,
53+
},
54+
DropTable {
55+
table_name: &'a str,
56+
},
57+
DropTempIndex {
58+
index_name: &'a str,
59+
table_name: &'a str,
60+
},
61+
DropTempTable {
62+
table_name: &'a str,
63+
},
64+
DropTempTrigger {
65+
trigger_name: &'a str,
66+
table_name: &'a str,
67+
},
68+
DropTempView {
69+
view_name: &'a str,
70+
},
71+
DropTrigger {
72+
trigger_name: &'a str,
73+
table_name: &'a str,
74+
},
75+
DropView {
76+
view_name: &'a str,
77+
},
78+
Insert {
79+
table_name: &'a str,
80+
},
81+
Pragma {
82+
pragma_name: &'a str,
83+
pragma_value: Option<&'a str>,
84+
},
85+
Read {
86+
table_name: &'a str,
87+
column_name: &'a str,
88+
},
89+
Select,
90+
Transaction {
91+
operation: TransactionOperation,
92+
},
93+
Update {
94+
table_name: &'a str,
95+
column_name: &'a str,
96+
},
97+
Attach {
98+
filename: &'a str,
99+
},
100+
Detach {
101+
database_name: &'a str,
102+
},
103+
AlterTable {
104+
database_name: &'a str,
105+
table_name: &'a str,
106+
},
107+
Reindex {
108+
index_name: &'a str,
109+
},
110+
Analyze {
111+
table_name: &'a str,
112+
},
113+
CreateVtable {
114+
table_name: &'a str,
115+
module_name: &'a str,
116+
},
117+
DropVtable {
118+
table_name: &'a str,
119+
module_name: &'a str,
120+
},
121+
Function {
122+
function_name: &'a str,
123+
},
124+
Savepoint {
125+
operation: TransactionOperation,
126+
savepoint_name: &'a str,
127+
},
128+
Recursive,
129+
}
130+
131+
impl<'a> AuthAction<'a> {
132+
pub(crate) fn from_raw(code: i32, arg1: Option<&'a str>, arg2: Option<&'a str>) -> Self {
133+
match (code, arg1, arg2) {
134+
(ffi::SQLITE_CREATE_INDEX, Some(index_name), Some(table_name)) => Self::CreateIndex {
135+
index_name,
136+
table_name,
137+
},
138+
(ffi::SQLITE_CREATE_TABLE, Some(table_name), _) => Self::CreateTable { table_name },
139+
(ffi::SQLITE_CREATE_TEMP_INDEX, Some(index_name), Some(table_name)) => {
140+
Self::CreateTempIndex {
141+
index_name,
142+
table_name,
143+
}
144+
}
145+
(ffi::SQLITE_CREATE_TEMP_TABLE, Some(table_name), _) => {
146+
Self::CreateTempTable { table_name }
147+
}
148+
(ffi::SQLITE_CREATE_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => {
149+
Self::CreateTempTrigger {
150+
trigger_name,
151+
table_name,
152+
}
153+
}
154+
(ffi::SQLITE_CREATE_TEMP_VIEW, Some(view_name), _) => {
155+
Self::CreateTempView { view_name }
156+
}
157+
(ffi::SQLITE_CREATE_TRIGGER, Some(trigger_name), Some(table_name)) => {
158+
Self::CreateTrigger {
159+
trigger_name,
160+
table_name,
161+
}
162+
}
163+
(ffi::SQLITE_CREATE_VIEW, Some(view_name), _) => Self::CreateView { view_name },
164+
(ffi::SQLITE_DELETE, Some(table_name), None) => Self::Delete { table_name },
165+
(ffi::SQLITE_DROP_INDEX, Some(index_name), Some(table_name)) => Self::DropIndex {
166+
index_name,
167+
table_name,
168+
},
169+
(ffi::SQLITE_DROP_TABLE, Some(table_name), _) => Self::DropTable { table_name },
170+
(ffi::SQLITE_DROP_TEMP_INDEX, Some(index_name), Some(table_name)) => {
171+
Self::DropTempIndex {
172+
index_name,
173+
table_name,
174+
}
175+
}
176+
(ffi::SQLITE_DROP_TEMP_TABLE, Some(table_name), _) => {
177+
Self::DropTempTable { table_name }
178+
}
179+
(ffi::SQLITE_DROP_TEMP_TRIGGER, Some(trigger_name), Some(table_name)) => {
180+
Self::DropTempTrigger {
181+
trigger_name,
182+
table_name,
183+
}
184+
}
185+
(ffi::SQLITE_DROP_TEMP_VIEW, Some(view_name), _) => Self::DropTempView { view_name },
186+
(ffi::SQLITE_DROP_TRIGGER, Some(trigger_name), Some(table_name)) => Self::DropTrigger {
187+
trigger_name,
188+
table_name,
189+
},
190+
(ffi::SQLITE_DROP_VIEW, Some(view_name), _) => Self::DropView { view_name },
191+
(ffi::SQLITE_INSERT, Some(table_name), _) => Self::Insert { table_name },
192+
(ffi::SQLITE_PRAGMA, Some(pragma_name), pragma_value) => Self::Pragma {
193+
pragma_name,
194+
pragma_value,
195+
},
196+
(ffi::SQLITE_READ, Some(table_name), Some(column_name)) => Self::Read {
197+
table_name,
198+
column_name,
199+
},
200+
(ffi::SQLITE_SELECT, ..) => Self::Select,
201+
(ffi::SQLITE_TRANSACTION, Some(operation_str), _) => Self::Transaction {
202+
operation: TransactionOperation::from_str(operation_str),
203+
},
204+
(ffi::SQLITE_UPDATE, Some(table_name), Some(column_name)) => Self::Update {
205+
table_name,
206+
column_name,
207+
},
208+
(ffi::SQLITE_ATTACH, Some(filename), _) => Self::Attach { filename },
209+
(ffi::SQLITE_DETACH, Some(database_name), _) => Self::Detach { database_name },
210+
(ffi::SQLITE_ALTER_TABLE, Some(database_name), Some(table_name)) => Self::AlterTable {
211+
database_name,
212+
table_name,
213+
},
214+
(ffi::SQLITE_REINDEX, Some(index_name), _) => Self::Reindex { index_name },
215+
(ffi::SQLITE_ANALYZE, Some(table_name), _) => Self::Analyze { table_name },
216+
(ffi::SQLITE_CREATE_VTABLE, Some(table_name), Some(module_name)) => {
217+
Self::CreateVtable {
218+
table_name,
219+
module_name,
220+
}
221+
}
222+
(ffi::SQLITE_DROP_VTABLE, Some(table_name), Some(module_name)) => Self::DropVtable {
223+
table_name,
224+
module_name,
225+
},
226+
(ffi::SQLITE_FUNCTION, _, Some(function_name)) => Self::Function { function_name },
227+
(ffi::SQLITE_SAVEPOINT, Some(operation_str), Some(savepoint_name)) => Self::Savepoint {
228+
operation: TransactionOperation::from_str(operation_str),
229+
savepoint_name,
230+
},
231+
(ffi::SQLITE_RECURSIVE, ..) => Self::Recursive,
232+
(code, arg1, arg2) => Self::Unknown { code, arg1, arg2 },
233+
}
234+
}
235+
}
236+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
237+
pub enum TransactionOperation {
238+
Unknown,
239+
Begin,
240+
Release,
241+
Rollback,
242+
}
243+
244+
impl TransactionOperation {
245+
fn from_str(op_str: &str) -> Self {
246+
match op_str {
247+
"BEGIN" => Self::Begin,
248+
"RELEASE" => Self::Release,
249+
"ROLLBACK" => Self::Rollback,
250+
_ => Self::Unknown,
251+
}
252+
}
253+
}
254+
255+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
256+
pub enum Authorization {
257+
Allow,
258+
Ignore,
259+
Deny,
260+
}

libsql/src/connection.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ use std::path::Path;
44
use std::sync::Arc;
55
use std::time::Duration;
66

7+
use crate::auth::{AuthContext, Authorization};
78
use crate::params::{IntoParams, Params};
89
use crate::rows::Rows;
910
use crate::statement::Statement;
1011
use crate::transaction::Transaction;
1112
use crate::{Result, TransactionBehavior};
1213

14+
pub type AuthHook = Arc<dyn Fn(&AuthContext) -> Authorization>;
15+
1316
#[async_trait::async_trait]
1417
pub(crate) trait Conn {
1518
async fn execute(&self, sql: &str, params: Params) -> Result<u64>;
@@ -43,6 +46,10 @@ pub(crate) trait Conn {
4346
fn load_extension(&self, _dylib_path: &Path, _entry_point: Option<&str>) -> Result<()> {
4447
Err(crate::Error::LoadExtensionNotSupported)
4548
}
49+
50+
fn authorizer(&self, _hook: Option<AuthHook>) -> Result<()> {
51+
Err(crate::Error::AuthorizerNotSupported)
52+
}
4653
}
4754

4855
/// A set of rows returned from `execute_batch`/`execute_transactional_batch`. It is essentially
@@ -258,6 +265,10 @@ impl Connection {
258265
) -> Result<()> {
259266
self.conn.load_extension(dylib_path.as_ref(), entry_point)
260267
}
268+
269+
pub fn authorizer(&self, hook: Option<AuthHook>) -> Result<()> {
270+
self.conn.authorizer(hook)
271+
}
261272
}
262273

263274
impl fmt::Debug for Connection {

libsql/src/errors.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ pub enum Error {
2121
SyncNotSupported(String), // Not in rusqlite
2222
#[error("Loading extension is only supported in local databases.")]
2323
LoadExtensionNotSupported, // Not in rusqlite
24+
#[error("Authorizer is only supported in local databases.")]
25+
AuthorizerNotSupported, // Not in rusqlite
2426
#[error("Column not found: {0}")]
2527
ColumnNotFound(i32), // Not in rusqlite
2628
#[error("Hrana: `{0}`")]

libsql/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ pub use errors::Error;
153153

154154
pub use params::params_from_iter;
155155

156+
mod auth;
156157
mod connection;
157158
mod database;
158159
mod load_extension_guard;
@@ -176,7 +177,8 @@ cfg_hrana! {
176177
}
177178

178179
pub use self::{
179-
connection::{BatchRows, Connection},
180+
auth::{AuthAction, AuthContext, Authorization},
181+
connection::{AuthHook, BatchRows, Connection},
180182
database::{Builder, Database},
181183
load_extension_guard::LoadExtensionGuard,
182184
rows::{Column, Row, Rows},

0 commit comments

Comments
 (0)