1
use itertools::Itertools;
2
use serde::{Deserialize, Serialize};
3

            
4
use crate::{
5
    admin::{group, role},
6
    connection::{Connection, SensitiveString},
7
    define_basic_unique_mapped_view,
8
    document::{CollectionDocument, Emit, KeyId},
9
    permissions::Permissions,
10
    schema::{Collection, NamedCollection, SerializedCollection},
11
};
12

            
13
/// A user that can authenticate with BonsaiDb.
14
100880
#[derive(Clone, Debug, Serialize, Deserialize, Default, Collection)]
15
#[collection(name = "user", authority = "khonsulabs", views = [ByName])]
16
#[collection(encryption_key = Some(KeyId::Master), encryption_optional, core = crate)]
17
pub struct User {
18
    /// The name of the role. Must be unique.
19
    pub username: String,
20
    /// The IDs of the user groups this user belongs to.
21
    pub groups: Vec<u64>,
22
    /// The IDs of the roles this user has been assigned.
23
    pub roles: Vec<u64>,
24

            
25
    /// The user's stored password hash.
26
    ///
27
    /// This field is not feature gated to prevent losing stored passwords if
28
    /// the `password-hashing` feature is disabled and then re-enabled and user
29
    /// records are updated in the meantime.
30
    #[serde(default)]
31
    pub argon_hash: Option<SensitiveString>,
32
}
33

            
34
impl User {
35
    /// Returns a default user with the given username.
36
177
    pub fn default_with_username(username: impl Into<String>) -> Self {
37
177
        Self {
38
177
            username: username.into(),
39
177
            ..Self::default()
40
177
        }
41
177
    }
42

            
43
    /// Calculates the effective permissions based on the groups and roles this
44
    /// user is assigned.
45
5
    pub async fn effective_permissions<C: Connection>(
46
5
        &self,
47
5
        admin: &C,
48
5
    ) -> Result<Permissions, crate::Error> {
49
        // List all of the groups that this user belongs to because of role associations.
50
5
        let role_groups = if self.roles.is_empty() {
51
5
            Vec::default()
52
        } else {
53
            let roles = role::Role::get_multiple(self.groups.iter().copied(), admin).await?;
54
            roles
55
                .into_iter()
56
                .flat_map(|doc| doc.contents.groups)
57
                .unique()
58
                .collect::<Vec<_>>()
59
        };
60
        // Retrieve all of the groups.
61
5
        let groups = if role_groups.is_empty() {
62
5
            group::PermissionGroup::get_multiple(self.groups.iter().copied(), admin).await?
63
        } else {
64
            let mut all_groups = role_groups;
65
            all_groups.extend(self.groups.iter().copied());
66
            all_groups.dedup();
67
            group::PermissionGroup::get_multiple(all_groups, admin).await?
68
        };
69

            
70
        // Combine the permissions from all the groups into one.
71
5
        let merged_permissions = Permissions::merged(
72
5
            groups
73
5
                .into_iter()
74
5
                .map(|group| Permissions::from(group.contents.statements))
75
5
                .collect::<Vec<_>>()
76
5
                .iter(),
77
5
        );
78
5

            
79
5
        Ok(merged_permissions)
80
5
    }
81
}
82

            
83
impl NamedCollection for User {
84
    type ByNameView = ByName;
85
}
86

            
87
define_basic_unique_mapped_view!(
88
    ByName,
89
    User,
90
    1,
91
    "by-name",
92
    String,
93
780
    |document: CollectionDocument<User>| { document.header.emit_key(document.contents.username) }
94
);