Skip to content

Commit 81fa360

Browse files
authored
libsql: Add authorizer support (#2020)
This adds support for `sqlite3_set_authorizer()`. The API follows rusqlite and the enumerations are lifted straight from rusqlite for compatibility.
2 parents 9a65514 + 45b40c3 commit 81fa360

File tree

7 files changed

+449
-3
lines changed

7 files changed

+449
-3
lines changed

libsql/src/auth.rs

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

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)