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

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

            
13
/// A user that can authenticate with BonsaiDb.
14
232320
#[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
    pub fn assume_identity<'name, Storage: StorageConnection>(
36
        name_or_id: impl Nameable<'name, u64>,
37
        storage: &Storage,
38
    ) -> Result<Storage::Authenticated, crate::Error> {
39
        storage.assume_identity(IdentityReference::User(name_or_id.name()?))
40
    }
41

            
42
    pub async fn assume_identity_async<'name, Storage: AsyncStorageConnection>(
43
        name_or_id: impl Nameable<'name, u64> + Send,
44
        storage: &Storage,
45
    ) -> Result<Storage::Authenticated, crate::Error> {
46
        storage
47
            .assume_identity(IdentityReference::User(name_or_id.name()?))
48
            .await
49
    }
50

            
51
    /// Returns a default user with the given username.
52
584
    pub fn default_with_username(username: impl Into<String>) -> Self {
53
584
        Self {
54
584
            username: username.into(),
55
584
            ..Self::default()
56
584
        }
57
584
    }
58

            
59
    /// Calculates the effective permissions based on the groups and roles this
60
    /// user is assigned.
61
400
    pub fn effective_permissions<C: Connection>(
62
400
        &self,
63
400
        admin: &C,
64
400
        inherit_permissions: &Permissions,
65
400
    ) -> Result<Permissions, crate::Error> {
66
        // List all of the groups that this user belongs to because of role associations.
67
400
        let role_groups = if self.roles.is_empty() {
68
400
            Vec::default()
69
        } else {
70
            let roles = role::Role::get_multiple(self.groups.iter(), admin)?;
71
            roles
72
                .into_iter()
73
                .flat_map(|doc| doc.contents.groups)
74
                .unique()
75
                .collect::<Vec<_>>()
76
        };
77
        // Retrieve all of the groups.
78
400
        let groups = if role_groups.is_empty() {
79
400
            group::PermissionGroup::get_multiple(self.groups.iter(), admin)?
80
        } else {
81
            let mut all_groups = role_groups;
82
            all_groups.extend(self.groups.iter().copied());
83
            all_groups.dedup();
84
            group::PermissionGroup::get_multiple(&all_groups, admin)?
85
        };
86

            
87
        // Combine the permissions from all the groups into one.
88
400
        let merged_permissions = Permissions::merged(
89
400
            groups
90
400
                .into_iter()
91
400
                .map(|group| Permissions::from(group.contents.statements))
92
400
                .collect::<Vec<_>>()
93
400
                .iter()
94
400
                .chain(std::iter::once(inherit_permissions)),
95
400
        );
96
400

            
97
400
        Ok(merged_permissions)
98
400
    }
99
}
100

            
101
impl NamedCollection for User {
102
    type ByNameView = ByName;
103
}
104

            
105
define_basic_unique_mapped_view!(
106
    ByName,
107
    User,
108
    1,
109
    "by-name",
110
    String,
111
2520
    |document: CollectionDocument<User>| { document.header.emit_key(document.contents.username) }
112
);