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

            
3
use std::time::Duration;
4

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

            
22
mod support;
23
use support::schema::Shape;
24

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

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

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

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

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

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

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

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

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

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

            
161
    // Give a moment for the listeners to start.
162
2
    tokio::time::sleep(Duration::from_millis(10)).await;
163

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

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

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

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

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

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

            
218
    // Shut the server down gracefully (or forcefully after 5 seconds).
219
2
    server.shutdown(Some(Duration::from_secs(5))).await?;
220

            
221
2
    Ok(())
222
2
}
223

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

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

            
246
2
    Ok(server)
247
2
}
248

            
249
1
#[test]
250
1
fn runs() {
251
1
    main().unwrap();
252
1
    main().unwrap();
253
1
}