bonsaidb_core/admin/
user.rs

1use itertools::Itertools;
2use serde::{Deserialize, Serialize};
3
4use crate::admin::{group, role};
5use crate::connection::{
6    AsyncStorageConnection, Connection, IdentityReference, SensitiveString, StorageConnection,
7};
8use crate::define_basic_unique_mapped_view;
9use crate::document::{CollectionDocument, Emit, KeyId};
10use crate::permissions::Permissions;
11use crate::schema::{Collection, Nameable, NamedCollection, SerializedCollection};
12
13/// A user that can authenticate with BonsaiDb.
14#[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)]
17pub 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
34impl 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    pub fn default_with_username(username: impl Into<String>) -> Self {
53        Self {
54            username: username.into(),
55            ..Self::default()
56        }
57    }
58
59    /// Calculates the effective permissions based on the groups and roles this
60    /// user is assigned.
61    pub fn effective_permissions<C: Connection>(
62        &self,
63        admin: &C,
64        inherit_permissions: &Permissions,
65    ) -> Result<Permissions, crate::Error> {
66        // List all of the groups that this user belongs to because of role associations.
67        let role_groups = if self.roles.is_empty() {
68            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        let groups = if role_groups.is_empty() {
79            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        let merged_permissions = Permissions::merged(
89            groups
90                .into_iter()
91                .map(|group| Permissions::from(group.contents.statements))
92                .collect::<Vec<_>>()
93                .iter()
94                .chain(std::iter::once(inherit_permissions)),
95        );
96
97        Ok(merged_permissions)
98    }
99}
100
101impl NamedCollection for User {
102    type ByNameView = ByName;
103}
104
105define_basic_unique_mapped_view!(
106    ByName,
107    User,
108    1,
109    "by-name",
110    String,
111    |document: CollectionDocument<User>| { document.header.emit_key(document.contents.username) }
112);