1
//! Shows basic usage of users and permissions.
2

            
3
use std::time::Duration;
4

            
5
use bonsaidb::client::url::Url;
6
use bonsaidb::client::AsyncClient;
7
use bonsaidb::core::admin::{PermissionGroup, Role};
8
use bonsaidb::core::connection::{
9
    AsyncStorageConnection, Authentication, AuthenticationMethod, SensitiveString,
10
};
11
use bonsaidb::core::permissions::bonsai::{
12
    BonsaiAction, DatabaseAction, DocumentAction, ServerAction,
13
};
14
use bonsaidb::core::permissions::{Permissions, Statement};
15
use bonsaidb::core::schema::{InsertError, SerializedCollection};
16
use bonsaidb::local::config::Builder;
17
use bonsaidb::server::{Server, ServerConfiguration};
18

            
19
mod support;
20
use support::schema::Shape;
21

            
22
#[tokio::main]
23
2
async fn main() -> anyhow::Result<()> {
24
2
    drop(env_logger::try_init());
25
24
    let server = setup_server().await?;
26
2

            
27
2
    // This example shows off basic user authentication as well as the ability
28
2
    // to assume roles. The server will be configured to only allow connected
29
2
    // users the ability to authenticate. All other usage will result in
30
2
    // PermissionDenied errors.
31
2
    //
32
2
    // We will create a user that belongs to a PermissionGroup that gives it the
33
2
    // ability to insert and read documents, but not delete them. This example
34
2
    // will demonstrate how to authenticate as the user, and shows how
35
2
    // permission is denied before authentication but is allowed on the
36
2
    // authenticated client.
37
2
    //
38
2
    // The other bit of setup we are going to do is create an administrators
39
2
    // group that the user belongs to. This permission group will allow the user
40
2
    // to assume any identity (user or role) in BonsaiDb. We are going to show
41
2
    // how to use this to escalate privileges by creating a "superuser" Role,
42
2
    // which belongs to a "superusers" group that grants all privileges.
43
2
    //
44
2
    // This example will finish by using the authenticated client to assume the
45
2
    // Superuser role and delete the record we inserted. While this is a complex
46
2
    // setup, it is a powerful pattern in Role Based Access Control which can
47
2
    // help protect users from accidentally performing a dangerous operation.
48
2

            
49
2
    // Create a database user, or get its ID if it already existed.
50
2
    let user_id = match server.create_user("ecton").await {
51
2
        Ok(id) => {
52
1
            // Set the user's password.
53
1
            server
54
1
                .set_user_password("ecton", SensitiveString::from("hunter2"))
55
2
                .await?;
56
2

            
57
2
            id
58
2
        }
59
2
        Err(bonsaidb::core::Error::UniqueKeyViolation {
60
2
            existing_document, ..
61
1
        }) => existing_document.id.deserialize()?,
62
2
        Err(other) => anyhow::bail!(other),
63
2
    };
64
2

            
65
2
    // Create an basic-users permission group, or get its ID if it already existed.
66
2
    let admin = server.admin().await;
67
2
    let users_group_id = match (PermissionGroup {
68
2
        name: String::from("basic-users"),
69
2
        statements: vec![Statement::for_any()
70
2
            .allowing(&BonsaiAction::Database(DatabaseAction::Document(
71
2
                DocumentAction::Insert,
72
2
            )))
73
2
            .allowing(&BonsaiAction::Database(DatabaseAction::Document(
74
2
                DocumentAction::Get,
75
2
            )))],
76
2
    }
77
2
    .push_into_async(&admin)
78
2
    .await)
79
2
    {
80
2
        Ok(doc) => doc.header.id,
81
2
        Err(InsertError {
82
2
            error:
83
2
                bonsaidb::core::Error::UniqueKeyViolation {
84
2
                    existing_document, ..
85
1
                },
86
1
            ..
87
1
        }) => existing_document.id.deserialize()?,
88
2
        Err(other) => anyhow::bail!(other),
89
2
    };
90
2

            
91
2
    // Make our user a member of the basic-users group.
92
2
    server
93
2
        .add_permission_group_to_user(user_id, users_group_id)
94
2
        .await?;
95
2

            
96
2
    // Create an superusers group, which has all permissions
97
2
    let superusers_group_id = match (PermissionGroup {
98
2
        name: String::from("superusers"),
99
2
        statements: vec![Statement::allow_all_for_any_resource()],
100
2
    }
101
2
    .push_into_async(&admin)
102
2
    .await)
103
2
    {
104
2
        Ok(doc) => doc.header.id,
105
2
        Err(InsertError {
106
2
            error:
107
2
                bonsaidb::core::Error::UniqueKeyViolation {
108
2
                    existing_document, ..
109
1
                },
110
1
            ..
111
1
        }) => existing_document.id.deserialize()?,
112
2
        Err(other) => anyhow::bail!(other),
113
2
    };
114
2

            
115
2
    let superuser_role_id = match (Role {
116
2
        name: String::from("superuser"),
117
2
        groups: vec![superusers_group_id],
118
2
    }
119
2
    .push_into_async(&admin)
120
2
    .await)
121
2
    {
122
2
        Ok(doc) => doc.header.id,
123
2
        Err(InsertError {
124
2
            error:
125
2
                bonsaidb::core::Error::UniqueKeyViolation {
126
2
                    existing_document, ..
127
1
                },
128
1
            ..
129
1
        }) => existing_document.id.deserialize()?,
130
2
        Err(other) => anyhow::bail!(other),
131
2
    };
132
2

            
133
2
    let administrators_group_id = match (PermissionGroup {
134
2
        name: String::from("administrators"),
135
2
        statements: vec![
136
2
            Statement::for_any().allowing(&BonsaiAction::Server(ServerAction::AssumeIdentity))
137
2
        ],
138
2
    }
139
2
    .push_into_async(&admin)
140
2
    .await)
141
2
    {
142
2
        Ok(doc) => doc.header.id,
143
2
        Err(InsertError {
144
2
            error:
145
2
                bonsaidb::core::Error::UniqueKeyViolation {
146
2
                    existing_document, ..
147
1
                },
148
1
            ..
149
1
        }) => existing_document.id.deserialize()?,
150
2
        Err(other) => anyhow::bail!(other),
151
2
    };
152
2

            
153
2
    // Make our user a member of the administrators group.
154
2
    server
155
2
        .add_permission_group_to_user(user_id, administrators_group_id)
156
2
        .await?;
157
2

            
158
2
    // Give a moment for the listeners to start.
159
2
    tokio::time::sleep(Duration::from_millis(10)).await;
160
2

            
161
2
    {
162
2
        let client = AsyncClient::build(Url::parse("bonsaidb://localhost")?)
163
2
            .with_certificate(
164
2
                server
165
2
                    .certificate_chain()
166
4
                    .await?
167
2
                    .into_end_entity_certificate(),
168
2
            )
169
2
            .build()?;
170
2
        let db = client.database::<Shape>("my-database").await?;
171
2

            
172
2
        // Before authenticating, inserting a shape shouldn't work.
173
2
        match Shape::new(3).push_into_async(&db).await {
174
2
            Err(InsertError {
175
2
                error: bonsaidb::core::Error::PermissionDenied(denied),
176
2
                ..
177
2
            }) => {
178
2
                log::info!(
179
2
                    "Permission was correctly denied before logging in: {:?}",
180
2
                    denied
181
2
                );
182
2
            }
183
2
            _ => unreachable!("permission shouldn't be allowed"),
184
2
        }
185
2

            
186
2
        // Now, log in and try again.
187
2
        let authenticated_client = client
188
2
            .authenticate(Authentication::password(
189
2
                "ecton",
190
2
                SensitiveString(String::from("hunter2")),
191
2
            )?)
192
2
            .await?;
193
2

            
194
2
        let db = authenticated_client
195
2
            .database::<Shape>("my-database")
196
2
            .await?;
197
2
        let shape_doc = Shape::new(3).push_into_async(&db).await?;
198
2
        println!("Successully inserted document {shape_doc:?}");
199
2

            
200
2
        // The "basic-users" group and "administrators" groups do not give
201
2
        // permission to delete documents:
202
2
        assert!(matches!(
203
2
            shape_doc.delete_async(&db).await.unwrap_err(),
204
2
            bonsaidb::core::Error::PermissionDenied { .. }
205
2
        ));
206
2

            
207
2
        // But we can assume the Superuser role to delete the document:
208
2
        let as_superuser =
209
2
            Role::assume_identity_async(superuser_role_id, &authenticated_client).await?;
210
2
        shape_doc
211
2
            .delete_async(&as_superuser.database::<Shape>("my-database").await?)
212
2
            .await?;
213
2
    }
214
2

            
215
2
    // Shut the server down gracefully (or forcefully after 5 seconds).
216
2
    server.shutdown(Some(Duration::from_secs(5))).await?;
217
2

            
218
2
    Ok(())
219
2
}
220

            
221
2
async fn setup_server() -> anyhow::Result<Server> {
222
2
    let server = Server::open(
223
2
        ServerConfiguration::new("users-server-data.bonsaidb")
224
2
            .default_permissions(Permissions::from(
225
2
                Statement::for_any()
226
2
                    .allowing(&BonsaiAction::Server(ServerAction::Connect))
227
2
                    .allowing(&BonsaiAction::Server(ServerAction::Authenticate(
228
2
                        AuthenticationMethod::PasswordHash,
229
2
                    ))),
230
2
            ))
231
2
            .with_schema::<Shape>()?,
232
    )
233
6
    .await?;
234
4
    if server.certificate_chain().await.is_err() {
235
10
        server.install_self_signed_certificate(true).await?;
236
1
    }
237
4
    server.create_database::<Shape>("my-database", true).await?;
238

            
239
    // Spawn our QUIC-based protocol listener.
240
2
    let task_server = server.clone();
241
14
    tokio::spawn(async move { task_server.listen_on(5645).await });
242
2

            
243
2
    Ok(server)
244
2
}
245

            
246
1
#[test]
247
1
fn runs() {
248
1
    main().unwrap();
249
1
    main().unwrap();
250
1
}