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
        connection::StorageConnection,
10
        keyvalue::KeyValue,
11
        permissions::{
12
            bonsai::{BonsaiAction, ServerAction},
13
            Statement,
14
        },
15
        schema::{InsertError, SerializedCollection},
16
        test_util::{BasicSchema, HarnessTest, TestDirectory},
17
    },
18
    local::config::Builder,
19
    server::{
20
        fabruic::Certificate,
21
        test_util::{initialize_basic_server, BASIC_SERVER_NAME},
22
        DefaultPermissions, Server, ServerConfiguration,
23
    },
24
};
25
use bonsaidb_core::{
26
    connection::{Authentication, SensitiveString},
27
    permissions::bonsai::AuthenticationMethod,
28
};
29
use once_cell::sync::Lazy;
30
use rand::{distributions::Alphanumeric, Rng};
31
use tokio::sync::Mutex;
32

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

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

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

            
48
62
    certificate.clone().unwrap()
49
62
}
50

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

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

            
77
61
        server.listen_on(6000).await.unwrap();
78
1
    });
79
1

            
80
1
    Ok(())
81
1
}
82

            
83
#[cfg(feature = "websockets")]
84
mod websockets {
85
    use super::*;
86

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

            
93
    impl WebsocketTestHarness {
94
30
        pub async fn new(test: HarnessTest) -> anyhow::Result<Self> {
95
30
            initialize_shared_server().await;
96
30
            let url = Url::parse("ws://localhost:6001")?;
97
30
            let client = Client::new(url.clone()).await?;
98

            
99
30
            let dbname = format!("websockets-{}", test);
100
30
            client
101
30
                .create_database::<BasicSchema>(&dbname, false)
102
30
                .await?;
103
30
            let db = client.database::<BasicSchema>(&dbname).await?;
104

            
105
30
            Ok(Self { client, url, db })
106
30
        }
107

            
108
2
        pub const fn server_name() -> &'static str {
109
2
            "websocket"
110
2
        }
111

            
112
2
        pub fn server(&self) -> &'_ Client {
113
2
            &self.client
114
2
        }
115

            
116
29
        pub async fn connect<'a, 'b>(&'a self) -> anyhow::Result<RemoteDatabase> {
117
29
            Ok(self.db.clone())
118
29
        }
119

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

            
130
26
        pub async fn shutdown(&self) -> anyhow::Result<()> {
131
26
            Ok(())
132
26
        }
133
    }
134

            
135
1
    #[tokio::test]
136
1
    async fn incompatible_client_version() -> anyhow::Result<()> {
137
1
        let certificate = initialize_shared_server().await;
138

            
139
1
        let url = Url::parse("ws://localhost:6001")?;
140
1
        let client = Client::build(url.clone())
141
1
            .with_certificate(certificate.clone())
142
1
            .with_protocol_version(INCOMPATIBLE_PROTOCOL_VERSION)
143
1
            .finish()
144
            .await?;
145

            
146
1
        check_incompatible_client(client).await
147
1
    }
148

            
149
38
    bonsaidb_core::define_connection_test_suite!(WebsocketTestHarness);
150

            
151
9
    bonsaidb_core::define_pubsub_test_suite!(WebsocketTestHarness);
152
262
    bonsaidb_core::define_kv_test_suite!(WebsocketTestHarness);
153
}
154

            
155
mod bonsai {
156
    use super::*;
157
    struct BonsaiTestHarness {
158
        client: Client,
159
        url: Url,
160
        certificate: Certificate,
161
        db: RemoteDatabase,
162
    }
163

            
164
    impl BonsaiTestHarness {
165
30
        pub async fn new(test: HarnessTest) -> anyhow::Result<Self> {
166
30
            let certificate = initialize_shared_server().await;
167

            
168
30
            let url = Url::parse(&format!(
169
30
                "bonsaidb://localhost:6000?server={}",
170
30
                BASIC_SERVER_NAME
171
30
            ))?;
172
30
            let client = Client::build(url.clone())
173
30
                .with_certificate(certificate.clone())
174
30
                .finish()
175
                .await?;
176

            
177
30
            let dbname = format!("bonsai-{}", test);
178
30
            client
179
30
                .create_database::<BasicSchema>(&dbname, false)
180
30
                .await?;
181
30
            let db = client.database::<BasicSchema>(&dbname).await?;
182

            
183
30
            Ok(Self {
184
30
                client,
185
30
                url,
186
30
                certificate,
187
30
                db,
188
30
            })
189
30
        }
190

            
191
2
        pub fn server_name() -> &'static str {
192
2
            "bonsai"
193
2
        }
194

            
195
2
        pub fn server(&self) -> &'_ Client {
196
2
            &self.client
197
2
        }
198

            
199
29
        pub async fn connect<'a, 'b>(&'a self) -> anyhow::Result<RemoteDatabase> {
200
29
            Ok(self.db.clone())
201
29
        }
202

            
203
        #[allow(dead_code)] // We will want this in the future but it's currently unused
204
        pub async fn connect_with_permissions(
205
            &self,
206
            statements: Vec<Statement>,
207
            label: &str,
208
        ) -> anyhow::Result<RemoteDatabase> {
209
            let client = Client::build(self.url.clone())
210
                .with_certificate(self.certificate.clone())
211
                .finish()
212
                .await?;
213
            assume_permissions(client, label, self.db.name(), statements).await
214
        }
215

            
216
26
        pub async fn shutdown(&self) -> anyhow::Result<()> {
217
26
            Ok(())
218
26
        }
219
    }
220

            
221
1
    #[tokio::test]
222
1
    async fn incompatible_client_version() -> anyhow::Result<()> {
223
1
        let certificate = initialize_shared_server().await;
224

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

            
235
1
        check_incompatible_client(client).await
236
1
    }
237

            
238
38
    bonsaidb_core::define_connection_test_suite!(BonsaiTestHarness);
239
9
    bonsaidb_core::define_pubsub_test_suite!(BonsaiTestHarness);
240
262
    bonsaidb_core::define_kv_test_suite!(BonsaiTestHarness);
241
}
242

            
243
2
async fn check_incompatible_client(client: Client) -> anyhow::Result<()> {
244
2
    match client
245
2
        .database::<()>("a database")
246
        .await?
247
2
        .set_numeric_key("a", 1_u64)
248
2
        .await
249
    {
250
2
        Err(bonsaidb_core::Error::Client(err)) => {
251
2
            assert!(
252
2
                err.contains("protocol version"),
253
                "unexpected error: {:?}",
254
                err
255
            );
256
        }
257
        other => unreachable!(
258
            "Unexpected result with invalid protocol version: {:?}",
259
            other
260
        ),
261
    }
262

            
263
2
    Ok(())
264
2
}
265

            
266
#[allow(dead_code)] // We will want this in the future but it's currently unused
267
async fn assume_permissions(
268
    connection: Client,
269
    label: &str,
270
    database_name: &str,
271
    statements: Vec<Statement>,
272
) -> anyhow::Result<RemoteDatabase> {
273
    let username = format!("{}-{}", database_name, label);
274
    let password = SensitiveString(
275
        rand::thread_rng()
276
            .sample_iter(&Alphanumeric)
277
            .take(8)
278
            .map(char::from)
279
            .collect(),
280
    );
281
    match connection.create_user(&username).await {
282
        Ok(user_id) => {
283
            connection
284
                .set_user_password(&username, password.clone())
285
                .await
286
                .unwrap();
287

            
288
            // Create an administrators permission group, or get its ID if it already existed.
289
            let admin = connection.database::<Admin>(ADMIN_DATABASE_NAME).await?;
290
            let administrator_group_id = match (PermissionGroup {
291
                name: String::from(label),
292
                statements,
293
            }
294
            .push_into(&admin)
295
            .await)
296
            {
297
                Ok(doc) => doc.header.id,
298
                Err(InsertError {
299
                    error:
300
                        bonsaidb_core::Error::UniqueKeyViolation {
301
                            existing_document, ..
302
                        },
303
                    ..
304
                }) => existing_document.id.deserialize()?,
305
                Err(other) => anyhow::bail!(other),
306
            };
307

            
308
            // Make our user a member of the administrators group.
309
            connection
310
                .add_permission_group_to_user(user_id, administrator_group_id)
311
                .await
312
                .unwrap();
313
        }
314
        Err(bonsaidb_core::Error::UniqueKeyViolation { .. }) => {}
315
        Err(other) => anyhow::bail!(other),
316
    };
317

            
318
    connection
319
        .authenticate(&username, Authentication::Password(password))
320
        .await
321
        .unwrap();
322

            
323
    Ok(connection.database::<BasicSchema>(database_name).await?)
324
}
325

            
326
1
#[tokio::test]
327
1
async fn authenticated_permissions_test() -> anyhow::Result<()> {
328
1
    let database_path = TestDirectory::new("authenticated-permissions");
329
1
    let server = Server::open(
330
1
        ServerConfiguration::new(&database_path)
331
1
            .default_permissions(Permissions::from(
332
1
                Statement::for_any()
333
1
                    .allowing(&BonsaiAction::Server(ServerAction::Connect))
334
1
                    .allowing(&BonsaiAction::Server(ServerAction::Authenticate(
335
1
                        AuthenticationMethod::PasswordHash,
336
1
                    ))),
337
1
            ))
338
1
            .authenticated_permissions(DefaultPermissions::AllowAll),
339
15
    )
340
15
    .await?;
341
10
    server.install_self_signed_certificate(false).await?;
342
1
    let certificate = server
343
1
        .certificate_chain()
344
1
        .await?
345
1
        .into_end_entity_certificate();
346
1

            
347
2
    server.create_user("ecton").await?;
348
1
    server
349
4
        .set_user_password("ecton", SensitiveString("hunter2".to_string()))
350
4
        .await?;
351
1
    tokio::spawn(async move {
352
3
        server.listen_on(6002).await?;
353
        Result::<(), anyhow::Error>::Ok(())
354
1
    });
355
1
    // Give the server time to listen
356
1
    tokio::time::sleep(Duration::from_millis(10)).await;
357

            
358
1
    let url = Url::parse("bonsaidb://localhost:6002")?;
359
1
    let client = Client::build(url)
360
1
        .with_certificate(certificate)
361
1
        .finish()
362
        .await?;
363
1
    match client.create_user("otheruser").await {
364
1
        Err(bonsaidb_core::Error::PermissionDenied(_)) => {}
365
        _ => unreachable!("should not have permission to create another user before logging in"),
366
    }
367

            
368
1
    client
369
1
        .authenticate(
370
1
            "ecton",
371
1
            Authentication::Password(SensitiveString(String::from("hunter2"))),
372
1
        )
373
1
        .await
374
1
        .unwrap();
375
1
    client
376
1
        .create_user("otheruser")
377
1
        .await
378
1
        .expect("should be able to create user after logging in");
379
1

            
380
1
    Ok(())
381
1
}