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

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

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

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

            
65
    // 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
    {
80
1
        Ok(doc) => doc.header.id,
81
        Err(InsertError {
82
            error:
83
                bonsaidb::core::Error::UniqueKeyViolation {
84
1
                    existing_document, ..
85
1
                },
86
1
            ..
87
1
        }) => existing_document.id.deserialize()?,
88
        Err(other) => anyhow::bail!(other),
89
    };
90

            
91
    // 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

            
96
    // 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
    {
104
1
        Ok(doc) => doc.header.id,
105
        Err(InsertError {
106
            error:
107
                bonsaidb::core::Error::UniqueKeyViolation {
108
1
                    existing_document, ..
109
1
                },
110
1
            ..
111
1
        }) => existing_document.id.deserialize()?,
112
        Err(other) => anyhow::bail!(other),
113
    };
114

            
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
    {
122
1
        Ok(doc) => doc.header.id,
123
        Err(InsertError {
124
            error:
125
                bonsaidb::core::Error::UniqueKeyViolation {
126
1
                    existing_document, ..
127
1
                },
128
1
            ..
129
1
        }) => existing_document.id.deserialize()?,
130
        Err(other) => anyhow::bail!(other),
131
    };
132

            
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
    {
142
1
        Ok(doc) => doc.header.id,
143
        Err(InsertError {
144
            error:
145
                bonsaidb::core::Error::UniqueKeyViolation {
146
1
                    existing_document, ..
147
1
                },
148
1
            ..
149
1
        }) => existing_document.id.deserialize()?,
150
        Err(other) => anyhow::bail!(other),
151
    };
152

            
153
    // 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

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

            
161
    {
162
2
        let client = AsyncClient::build(Url::parse("bonsaidb://localhost")?)
163
            .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

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

            
186
        // 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

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

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

            
207
        // 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
        shape_doc
211
2
            .delete_async(&as_superuser.database::<Shape>("my-database").await?)
212
2
            .await?;
213
    }
214

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

            
218
2
    Ok(())
219
}
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
}