1
1
use std::time::Duration;
2

            
3
use bonsaidb::{
4
    client::{url::Url, Client, RemoteDatabase},
5
    core::{
6
        actionable::Permissions,
7
        admin::{Admin, PermissionGroup, ADMIN_DATABASE_NAME},
8
        circulate::flume,
9
        keyvalue::AsyncKeyValue,
10
        permissions::{
11
            bonsai::{BonsaiAction, ServerAction},
12
            Statement,
13
        },
14
        schema::{InsertError, SerializedCollection},
15
        test_util::{BasicSchema, HarnessTest, TestDirectory},
16
    },
17
    local::config::Builder,
18
    server::{
19
        fabruic::Certificate,
20
        test_util::{initialize_basic_server, BASIC_SERVER_NAME},
21
        DefaultPermissions, Server, ServerConfiguration,
22
    },
23
};
24
use bonsaidb_core::{
25
    connection::{Authentication, SensitiveString},
26
    permissions::bonsai::AuthenticationMethod,
27
};
28
use once_cell::sync::Lazy;
29
use rand::{distributions::Alphanumeric, Rng};
30
use tokio::sync::Mutex;
31

            
32
const INCOMPATIBLE_PROTOCOL_VERSION: &str = "otherprotocol";
33

            
34
95
async fn initialize_shared_server() -> Certificate {
35
95
    static CERTIFICATE: Lazy<Mutex<Option<Certificate>>> = Lazy::new(|| Mutex::new(None));
36
95
    drop(env_logger::try_init());
37
95
    let mut certificate = CERTIFICATE.lock().await;
38
95
    if certificate.is_none() {
39
1
        let (sender, receiver) = flume::bounded(1);
40
1
        std::thread::spawn(|| run_shared_server(sender));
41

            
42
1
        *certificate = Some(receiver.recv_async().await.unwrap());
43
1
        // Give the server time to start listening
44
1
        tokio::time::sleep(Duration::from_millis(1000)).await;
45
94
    }
46

            
47
95
    certificate.clone().unwrap()
48
95
}
49

            
50
1
fn run_shared_server(certificate_sender: flume::Sender<Certificate>) -> anyhow::Result<()> {
51
1
    let rt = tokio::runtime::Runtime::new()?;
52
1
    rt.block_on(async move {
53
1
        let directory = TestDirectory::new("shared-server");
54
15
        let server = initialize_basic_server(directory.as_ref()).await.unwrap();
55
1
        certificate_sender
56
1
            .send(
57
1
                server
58
2
                    .certificate_chain()
59
2
                    .await
60
1
                    .unwrap()
61
1
                    .into_end_entity_certificate(),
62
1
            )
63
1
            .unwrap();
64
1

            
65
1
        #[cfg(feature = "websockets")]
66
1
        {
67
1
            let task_server = server.clone();
68
1
            tokio::spawn(async move {
69
1
                task_server
70
63
                    .listen_for_websockets_on("localhost:6001", false)
71
63
                    .await
72
                    .unwrap();
73
1
            });
74
1
        }
75
1

            
76
64
        server.listen_on(6000).await.unwrap();
77
1
    });
78
1

            
79
1
    Ok(())
80
1
}
81

            
82
#[cfg(feature = "websockets")]
83
mod websockets {
84
    use tokio::runtime::Runtime;
85

            
86
    use super::*;
87

            
88
    struct WebsocketTestHarness {
89
        client: Client,
90
        url: Url,
91
        db: RemoteDatabase,
92
    }
93

            
94
    impl WebsocketTestHarness {
95
31
        pub async fn new(test: HarnessTest) -> anyhow::Result<Self> {
96
            use bonsaidb_core::connection::AsyncStorageConnection;
97

            
98
31
            initialize_shared_server().await;
99
31
            let url = Url::parse("ws://localhost:6001")?;
100
31
            let client = Client::new(url.clone())?;
101

            
102
31
            let dbname = format!("websockets-{}", test);
103
31
            client
104
31
                .create_database::<BasicSchema>(&dbname, false)
105
31
                .await?;
106
31
            let db = client.database::<BasicSchema>(&dbname).await?;
107

            
108
31
            Ok(Self { client, url, db })
109
31
        }
110

            
111
2
        pub const fn server_name() -> &'static str {
112
2
            "websocket"
113
2
        }
114

            
115
2
        pub fn server(&self) -> &Client {
116
2
            &self.client
117
2
        }
118

            
119
30
        pub async fn connect(&self) -> anyhow::Result<RemoteDatabase> {
120
30
            Ok(self.db.clone())
121
30
        }
122

            
123
        #[allow(dead_code)] // We will want this in the future but it's currently unused
124
        pub async fn connect_with_permissions(
125
            &self,
126
            permissions: Vec<Statement>,
127
            label: &str,
128
        ) -> anyhow::Result<RemoteDatabase> {
129
            let client = Client::new(self.url.clone())?;
130
            assume_permissions(client, label, self.db.name(), permissions).await
131
        }
132

            
133
26
        pub async fn shutdown(&self) -> anyhow::Result<()> {
134
26
            Ok(())
135
26
        }
136
    }
137

            
138
38
    bonsaidb_core::define_async_connection_test_suite!(WebsocketTestHarness);
139

            
140
10
    bonsaidb_core::define_async_pubsub_test_suite!(WebsocketTestHarness);
141
259
    bonsaidb_core::define_async_kv_test_suite!(WebsocketTestHarness);
142

            
143
    struct BlockingWebsocketTestHarness {
144
        client: Client,
145
        // url: Url,
146
        db: RemoteDatabase,
147
    }
148

            
149
    impl BlockingWebsocketTestHarness {
150
31
        pub fn new(test: HarnessTest) -> anyhow::Result<Self> {
151
            use bonsaidb_core::connection::StorageConnection;
152
31
            let runtime = Runtime::new()?;
153
31
            runtime.block_on(initialize_shared_server());
154
31
            let url = Url::parse("ws://localhost:6001")?;
155
31
            let client = Client::new(url)?;
156

            
157
31
            let dbname = format!("blocking-websockets-{}", test);
158
31
            client.create_database::<BasicSchema>(&dbname, false)?;
159
31
            let db = client.database::<BasicSchema>(&dbname)?;
160

            
161
31
            Ok(Self { client, db })
162
31
        }
163

            
164
2
        pub const fn server_name() -> &'static str {
165
2
            "websocket-blocking"
166
2
        }
167

            
168
2
        pub fn server(&self) -> &Client {
169
2
            &self.client
170
2
        }
171

            
172
30
        pub fn connect(&self) -> anyhow::Result<RemoteDatabase> {
173
30
            Ok(self.db.clone())
174
30
        }
175

            
176
        // #[allow(dead_code)] // We will want this in the future but it's currently unused
177
        // pub  fn connect_with_permissions(
178
        //     &self,
179
        //     permissions: Vec<Statement>,
180
        //     label: &str,
181
        // ) -> anyhow::Result<RemoteDatabase> {
182
        //     let client = Client::new(self.url.clone())?;
183
        //     assume_permissions(client, label, self.db.name(), permissions)
184
        // }
185

            
186
26
        pub fn shutdown(&self) -> anyhow::Result<()> {
187
26
            Ok(())
188
26
        }
189
    }
190

            
191
1
    #[tokio::test]
192
1
    async fn incompatible_client_version() -> anyhow::Result<()> {
193
1
        let certificate = initialize_shared_server().await;
194

            
195
1
        let url = Url::parse("ws://localhost:6001")?;
196
1
        let client = Client::build(url.clone())
197
1
            .with_certificate(certificate.clone())
198
1
            .with_protocol_version(INCOMPATIBLE_PROTOCOL_VERSION)
199
1
            .finish()?;
200

            
201
1
        check_incompatible_client(client).await
202
1
    }
203

            
204
19
    bonsaidb_core::define_blocking_connection_test_suite!(BlockingWebsocketTestHarness);
205

            
206
5
    bonsaidb_core::define_blocking_pubsub_test_suite!(BlockingWebsocketTestHarness);
207
249
    bonsaidb_core::define_blocking_kv_test_suite!(BlockingWebsocketTestHarness);
208
}
209

            
210
mod bonsai {
211
    use super::*;
212
    struct BonsaiTestHarness {
213
        client: Client,
214
        url: Url,
215
        certificate: Certificate,
216
        db: RemoteDatabase,
217
    }
218

            
219
    impl BonsaiTestHarness {
220
31
        pub async fn new(test: HarnessTest) -> anyhow::Result<Self> {
221
            use bonsaidb_core::connection::AsyncStorageConnection;
222
31
            let certificate = initialize_shared_server().await;
223

            
224
31
            let url = Url::parse(&format!(
225
31
                "bonsaidb://localhost:6000?server={}",
226
31
                BASIC_SERVER_NAME
227
31
            ))?;
228
31
            let client = Client::build(url.clone())
229
31
                .with_certificate(certificate.clone())
230
31
                .finish()?;
231

            
232
31
            let dbname = format!("bonsai-{}", test);
233
31
            client
234
31
                .create_database::<BasicSchema>(&dbname, false)
235
31
                .await?;
236
31
            let db = client.database::<BasicSchema>(&dbname).await?;
237

            
238
31
            Ok(Self {
239
31
                client,
240
31
                url,
241
31
                certificate,
242
31
                db,
243
31
            })
244
31
        }
245

            
246
2
        pub fn server_name() -> &'static str {
247
2
            "bonsai"
248
2
        }
249

            
250
2
        pub fn server(&self) -> &'_ Client {
251
2
            &self.client
252
2
        }
253

            
254
30
        pub async fn connect<'a, 'b>(&'a self) -> anyhow::Result<RemoteDatabase> {
255
30
            Ok(self.db.clone())
256
30
        }
257

            
258
        #[allow(dead_code)] // We will want this in the future but it's currently unused
259
        pub async fn connect_with_permissions(
260
            &self,
261
            statements: Vec<Statement>,
262
            label: &str,
263
        ) -> anyhow::Result<RemoteDatabase> {
264
            let client = Client::build(self.url.clone())
265
                .with_certificate(self.certificate.clone())
266
                .finish()?;
267
            assume_permissions(client, label, self.db.name(), statements).await
268
        }
269

            
270
26
        pub async fn shutdown(&self) -> anyhow::Result<()> {
271
26
            Ok(())
272
26
        }
273
    }
274

            
275
1
    #[tokio::test]
276
1
    async fn incompatible_client_version() -> anyhow::Result<()> {
277
1
        let certificate = initialize_shared_server().await;
278

            
279
1
        let url = Url::parse(&format!(
280
1
            "bonsaidb://localhost:6000?server={}",
281
1
            BASIC_SERVER_NAME
282
1
        ))?;
283
1
        let client = Client::build(url.clone())
284
1
            .with_certificate(certificate.clone())
285
1
            .with_protocol_version(INCOMPATIBLE_PROTOCOL_VERSION)
286
1
            .finish()?;
287

            
288
1
        check_incompatible_client(client).await
289
1
    }
290

            
291
38
    bonsaidb_core::define_async_connection_test_suite!(BonsaiTestHarness);
292
10
    bonsaidb_core::define_async_pubsub_test_suite!(BonsaiTestHarness);
293
258
    bonsaidb_core::define_async_kv_test_suite!(BonsaiTestHarness);
294
}
295

            
296
2
async fn check_incompatible_client(client: Client) -> anyhow::Result<()> {
297
2
    use bonsaidb_core::connection::AsyncStorageConnection;
298
2
    match client
299
2
        .database::<()>("a database")
300
        .await?
301
2
        .set_numeric_key("a", 1_u64)
302
2
        .await
303
    {
304
2
        Err(bonsaidb_core::Error::Client(err)) => {
305
2
            assert!(
306
2
                err.contains("protocol version"),
307
                "unexpected error: {:?}",
308
                err
309
            );
310
        }
311
        other => unreachable!(
312
            "Unexpected result with invalid protocol version: {:?}",
313
            other
314
        ),
315
    }
316

            
317
2
    Ok(())
318
2
}
319

            
320
#[allow(dead_code)] // We will want this in the future but it's currently unused
321
async fn assume_permissions(
322
    connection: Client,
323
    label: &str,
324
    database_name: &str,
325
    statements: Vec<Statement>,
326
) -> anyhow::Result<RemoteDatabase> {
327
    use bonsaidb_core::connection::AsyncStorageConnection;
328
    let username = format!("{}-{}", database_name, label);
329
    let password = SensitiveString(
330
        rand::thread_rng()
331
            .sample_iter(&Alphanumeric)
332
            .take(8)
333
            .map(char::from)
334
            .collect(),
335
    );
336
    match connection.create_user(&username).await {
337
        Ok(user_id) => {
338
            connection
339
                .set_user_password(&username, password.clone())
340
                .await
341
                .unwrap();
342

            
343
            // Create an administrators permission group, or get its ID if it already existed.
344
            let admin = connection.database::<Admin>(ADMIN_DATABASE_NAME).await?;
345
            let administrator_group_id = match (PermissionGroup {
346
                name: String::from(label),
347
                statements,
348
            }
349
            .push_into_async(&admin)
350
            .await)
351
            {
352
                Ok(doc) => doc.header.id,
353
                Err(InsertError {
354
                    error:
355
                        bonsaidb_core::Error::UniqueKeyViolation {
356
                            existing_document, ..
357
                        },
358
                    ..
359
                }) => existing_document.id.deserialize()?,
360
                Err(other) => anyhow::bail!(other),
361
            };
362

            
363
            // Make our user a member of the administrators group.
364
            connection
365
                .add_permission_group_to_user(user_id, administrator_group_id)
366
                .await
367
                .unwrap();
368
        }
369
        Err(bonsaidb_core::Error::UniqueKeyViolation { .. }) => {}
370
        Err(other) => anyhow::bail!(other),
371
    };
372

            
373
    connection
374
        .authenticate(&username, Authentication::Password(password))
375
        .await
376
        .unwrap();
377

            
378
    Ok(connection.database::<BasicSchema>(database_name).await?)
379
}
380

            
381
1
#[tokio::test]
382
1
async fn authenticated_permissions_test() -> anyhow::Result<()> {
383
1
    use bonsaidb_core::connection::AsyncStorageConnection;
384
1
    let database_path = TestDirectory::new("authenticated-permissions");
385
1
    let server = Server::open(
386
1
        ServerConfiguration::new(&database_path)
387
1
            .default_permissions(Permissions::from(
388
1
                Statement::for_any()
389
1
                    .allowing(&BonsaiAction::Server(ServerAction::Connect))
390
1
                    .allowing(&BonsaiAction::Server(ServerAction::Authenticate(
391
1
                        AuthenticationMethod::PasswordHash,
392
1
                    ))),
393
1
            ))
394
1
            .authenticated_permissions(DefaultPermissions::AllowAll),
395
3
    )
396
3
    .await?;
397
10
    server.install_self_signed_certificate(false).await?;
398
1
    let certificate = server
399
2
        .certificate_chain()
400
2
        .await?
401
1
        .into_end_entity_certificate();
402
1

            
403
1
    server.create_user("ecton").await?;
404
1
    server
405
1
        .set_user_password("ecton", SensitiveString("hunter2".to_string()))
406
1
        .await?;
407
1
    tokio::spawn(async move {
408
5
        server.listen_on(6002).await?;
409
        Result::<(), anyhow::Error>::Ok(())
410
1
    });
411
1
    // Give the server time to listen
412
1
    tokio::time::sleep(Duration::from_millis(10)).await;
413

            
414
1
    let url = Url::parse("bonsaidb://localhost:6002")?;
415
1
    let client = Client::build(url).with_certificate(certificate).finish()?;
416
1
    match client.create_user("otheruser").await {
417
1
        Err(bonsaidb_core::Error::PermissionDenied(_)) => {}
418
        other => unreachable!(
419
            "should not have permission to create another user before logging in: {other:?}"
420
        ),
421
    }
422

            
423
1
    let authenticated_client = client
424
1
        .authenticate(
425
1
            "ecton",
426
1
            Authentication::Password(SensitiveString(String::from("hunter2"))),
427
1
        )
428
1
        .await
429
1
        .unwrap();
430
1
    authenticated_client
431
1
        .create_user("otheruser")
432
1
        .await
433
1
        .expect("should be able to create user after logging in");
434
1

            
435
1
    Ok(())
436
1
}