2020-11-19 18:20:14 +00:00
|
|
|
//! Several structs representing data about users
|
|
|
|
//!
|
|
|
|
//! This module contains any structs needed to store and retrieve data about users. The
|
|
|
|
//! different varieties have different purposes and come from different places.
|
|
|
|
//!
|
|
|
|
//! [`User`] is the most common for of user struct, and typically comes from calling
|
|
|
|
//! [`Request::user()`](crate::types::Request::user()). This is an enum with several
|
2020-11-19 22:00:32 +00:00
|
|
|
//! variants, and can be specialized into a [`NotSignedInUser`] or a [`RegisteredUser`] if
|
2020-11-19 18:20:14 +00:00
|
|
|
//! the user has presented a certificate. These two subtypes have more specific
|
|
|
|
//! information, like the user's username and active certificate.
|
|
|
|
//!
|
2020-11-19 22:00:32 +00:00
|
|
|
//! [`RegisteredUser`] is particularly signifigant in that this is the struct used to modify
|
2020-11-19 21:54:29 +00:00
|
|
|
//! the data stored for almost all users. This is accomplished through the
|
2020-11-19 22:00:32 +00:00
|
|
|
//! [`as_mut()`](RegisteredUser::as_mut) method. Changes made this way must be persisted
|
|
|
|
//! using [`save()`](RegisteredUser::save()) or by dropping the user.
|
2020-11-19 19:25:36 +00:00
|
|
|
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
2020-11-16 06:13:16 +00:00
|
|
|
use sled::Transactional;
|
|
|
|
|
|
|
|
use crate::user_management::UserManager;
|
|
|
|
use crate::user_management::Result;
|
|
|
|
|
2020-11-23 03:24:36 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")]
|
2020-11-19 21:25:13 +00:00
|
|
|
const ARGON2_CONFIG: argon2::Config = argon2::Config {
|
|
|
|
ad: &[],
|
|
|
|
hash_length: 32,
|
|
|
|
lanes: 1,
|
|
|
|
mem_cost: 4096,
|
|
|
|
secret: &[],
|
|
|
|
thread_mode: argon2::ThreadMode::Sequential,
|
|
|
|
time_cost: 3,
|
|
|
|
variant: argon2::Variant::Argon2id,
|
|
|
|
version: argon2::Version::Version13,
|
|
|
|
};
|
|
|
|
|
2020-11-23 03:24:36 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")]
|
2020-11-19 21:25:13 +00:00
|
|
|
lazy_static::lazy_static! {
|
|
|
|
static ref RANDOM: ring::rand::SystemRandom = ring::rand::SystemRandom::new();
|
|
|
|
}
|
|
|
|
|
2020-11-16 06:13:16 +00:00
|
|
|
/// An struct corresponding to the data stored in the user tree
|
2020-11-16 14:21:24 +00:00
|
|
|
///
|
|
|
|
/// In order to generate a full user obj, you need to perform a lookup with a specific
|
|
|
|
/// certificate.
|
2020-11-16 06:13:16 +00:00
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
2020-11-19 21:54:29 +00:00
|
|
|
pub (crate) struct PartialUser<UserData> {
|
2020-11-16 14:21:24 +00:00
|
|
|
pub data: UserData,
|
2020-12-01 19:43:15 +00:00
|
|
|
pub certificates: Vec<[u8; 32]>,
|
2020-11-23 03:24:36 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")]
|
2020-11-19 21:25:13 +00:00
|
|
|
pub pass_hash: Option<(Vec<u8>, [u8; 32])>,
|
2020-11-16 14:21:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<UserData> PartialUser<UserData> {
|
|
|
|
|
|
|
|
/// Write user data to the database
|
|
|
|
///
|
|
|
|
/// This MUST be called if the user data is modified using the AsMut trait, or else
|
|
|
|
/// changes will not be written to the database
|
2020-11-19 21:54:29 +00:00
|
|
|
fn store(&self, tree: &sled::Tree, username: impl AsRef<[u8]>) -> Result<()>
|
2020-11-16 14:21:24 +00:00
|
|
|
where
|
|
|
|
UserData: Serialize
|
|
|
|
{
|
|
|
|
tree.insert(
|
|
|
|
&username,
|
|
|
|
bincode::serialize(&self)?,
|
|
|
|
)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-16 06:13:16 +00:00
|
|
|
/// Any information about the connecting user
|
|
|
|
#[derive(Clone, Debug)]
|
2020-11-19 19:25:36 +00:00
|
|
|
pub enum User<UserData: Serialize + DeserializeOwned> {
|
2020-11-16 06:13:16 +00:00
|
|
|
/// A user who is connected without using a client certificate.
|
|
|
|
///
|
|
|
|
/// This could be a user who has an account but just isn't presenting a certificate at
|
|
|
|
/// the minute, a user whose client does not support client certificates, or a user
|
|
|
|
/// who has not yet created a certificate for the site
|
|
|
|
Unauthenticated,
|
|
|
|
|
|
|
|
/// A user who is connecting with a certificate that isn't connected to an account
|
|
|
|
///
|
|
|
|
/// This is typically a new user who hasn't set up an account yet, or a user
|
|
|
|
/// connecting with a new certificate that needs to be added to an existing account.
|
|
|
|
NotSignedIn(NotSignedInUser),
|
|
|
|
|
|
|
|
/// A user connecting with an identified account
|
2020-11-19 22:00:32 +00:00
|
|
|
SignedIn(RegisteredUser<UserData>),
|
2020-11-16 06:13:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
/// Data about a user with a certificate not associated with an account
|
|
|
|
///
|
|
|
|
/// For more information about the user lifecycle and sign-in stages, see [`User`]
|
|
|
|
pub struct NotSignedInUser {
|
2020-12-01 19:43:15 +00:00
|
|
|
pub (crate) certificate: [u8; 32],
|
2020-11-16 06:13:16 +00:00
|
|
|
pub (crate) manager: UserManager,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl NotSignedInUser {
|
2020-11-19 18:20:14 +00:00
|
|
|
/// Register a new user with this certificate.
|
|
|
|
///
|
|
|
|
/// This creates a new user & user data entry in the database with the given username.
|
|
|
|
/// From now on, when this user logs in with this certificate, they will be
|
|
|
|
/// automatically authenticated, and their user data automatically retrieved.
|
2020-11-16 06:13:16 +00:00
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
/// The provided username must be unique, or else an error will be raised.
|
|
|
|
///
|
|
|
|
/// Additional errors might occur if there is a problem writing to the database
|
2020-11-19 19:25:36 +00:00
|
|
|
pub fn register<UserData: Serialize + DeserializeOwned + Default>(
|
2020-11-16 06:13:16 +00:00
|
|
|
self,
|
|
|
|
username: String,
|
2020-11-19 22:00:32 +00:00
|
|
|
) -> Result<RegisteredUser<UserData>> {
|
2020-11-16 06:13:16 +00:00
|
|
|
if self.manager.users.contains_key(username.as_str())? {
|
|
|
|
Err(super::UserManagerError::UsernameNotUnique)
|
|
|
|
} else {
|
2020-11-28 19:23:39 +00:00
|
|
|
info!("User {} registered!", username);
|
|
|
|
|
2020-11-19 22:41:32 +00:00
|
|
|
let mut newser = RegisteredUser::new(
|
|
|
|
username,
|
2020-12-01 19:43:15 +00:00
|
|
|
Some(self.certificate),
|
2020-11-19 19:25:36 +00:00
|
|
|
self.manager,
|
|
|
|
PartialUser {
|
2020-11-16 06:13:16 +00:00
|
|
|
data: UserData::default(),
|
2020-11-19 22:41:32 +00:00
|
|
|
certificates: Vec::with_capacity(1),
|
2020-11-23 03:24:36 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")]
|
2020-11-16 06:13:16 +00:00
|
|
|
pass_hash: None,
|
|
|
|
},
|
2020-11-19 19:25:36 +00:00
|
|
|
);
|
2020-11-16 06:13:16 +00:00
|
|
|
|
2020-11-19 22:41:32 +00:00
|
|
|
// As a nice bonus, calling add_certificate with a user not yet in the
|
|
|
|
// database creates the user and adds the certificate in a single transaction.
|
|
|
|
// Because of this, we can delegate here ^^
|
|
|
|
newser.add_certificate(self.certificate)?;
|
2020-11-16 06:13:16 +00:00
|
|
|
|
|
|
|
Ok(newser)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-23 03:24:36 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")]
|
2020-11-16 06:13:16 +00:00
|
|
|
/// Attach this certificate to an existing user
|
|
|
|
///
|
2020-11-19 18:20:14 +00:00
|
|
|
/// Try to add this certificate to another user using a username and password. If
|
|
|
|
/// successful, the user this certificate is attached to will be able to automatically
|
|
|
|
/// log in with either this certificate or any of the certificates they already had
|
|
|
|
/// registered.
|
|
|
|
///
|
2020-11-19 22:41:32 +00:00
|
|
|
/// This method can check the user's password to ensure that they match before
|
|
|
|
/// registering. If you want to skip this verification, perhaps because you've
|
|
|
|
/// already verified that this user owns this account, then you can pass [`None`] as
|
|
|
|
/// the password to skip the password check.
|
|
|
|
///
|
2020-11-19 22:00:32 +00:00
|
|
|
/// This method returns the new RegisteredUser instance representing the now-attached
|
2020-11-19 22:41:32 +00:00
|
|
|
/// user, or [`None`] if the username and password didn't match.
|
|
|
|
///
|
|
|
|
/// Because this method both performs a bcrypt verification and a database access, it
|
|
|
|
/// should be considered expensive.
|
|
|
|
///
|
|
|
|
/// If you already have a [`RegisteredUser`] that you would like to attach a
|
|
|
|
/// certificate to, consider using [`RegisteredUser::add_certificate()`]
|
2020-11-16 06:13:16 +00:00
|
|
|
///
|
|
|
|
/// # Errors
|
2020-11-22 04:03:56 +00:00
|
|
|
/// This will error if the user has yet to set a password.
|
2020-11-16 06:13:16 +00:00
|
|
|
///
|
|
|
|
/// Additional errors might occur if an error occurs during database lookup and
|
|
|
|
/// deserialization
|
2020-11-19 19:25:36 +00:00
|
|
|
pub fn attach<UserData: Serialize + DeserializeOwned>(
|
2020-11-19 22:41:32 +00:00
|
|
|
self,
|
2020-11-22 04:03:56 +00:00
|
|
|
username: &str,
|
|
|
|
password: Option<&[u8]>,
|
2020-11-19 22:41:32 +00:00
|
|
|
) -> Result<Option<RegisteredUser<UserData>>> {
|
|
|
|
if let Some(mut user) = self.manager.lookup_user(username)? {
|
|
|
|
// Perform password check, if caller wants
|
|
|
|
if let Some(password) = password {
|
|
|
|
if !user.check_password(password)? {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-01 19:43:15 +00:00
|
|
|
info!("User {} attached certificate with fingerprint {:x?}", username, &self.certificate[..]);
|
|
|
|
user.add_certificate(self.certificate)?;
|
2020-11-27 23:36:24 +00:00
|
|
|
user.active_certificate = Some(self.certificate);
|
2020-11-19 22:41:32 +00:00
|
|
|
Ok(Some(user))
|
|
|
|
} else {
|
|
|
|
Ok(None)
|
|
|
|
}
|
2020-11-16 06:13:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
/// Data about a logged in user
|
|
|
|
///
|
|
|
|
/// For more information about the user lifecycle and sign-in stages, see [`User`]
|
2020-11-19 22:00:32 +00:00
|
|
|
pub struct RegisteredUser<UserData: Serialize + DeserializeOwned> {
|
2020-11-19 20:21:26 +00:00
|
|
|
username: String,
|
2020-12-01 19:43:15 +00:00
|
|
|
active_certificate: Option<[u8; 32]>,
|
2020-11-19 20:21:26 +00:00
|
|
|
manager: UserManager,
|
|
|
|
inner: PartialUser<UserData>,
|
2020-11-19 22:00:32 +00:00
|
|
|
/// Indicates that [`RegisteredUser::as_mut()`] has been called, but [`RegisteredUser::save()`] has not
|
2020-11-19 19:25:36 +00:00
|
|
|
has_changed: bool,
|
2020-11-16 06:13:16 +00:00
|
|
|
}
|
|
|
|
|
2020-11-19 22:00:32 +00:00
|
|
|
impl<UserData: Serialize + DeserializeOwned> RegisteredUser<UserData> {
|
2020-11-19 19:25:36 +00:00
|
|
|
|
|
|
|
/// Create a new user from parts
|
|
|
|
pub (crate) fn new(
|
|
|
|
username: String,
|
2020-12-01 19:43:15 +00:00
|
|
|
active_certificate: Option<[u8; 32]>,
|
2020-11-19 19:25:36 +00:00
|
|
|
manager: UserManager,
|
|
|
|
inner: PartialUser<UserData>
|
|
|
|
) -> Self {
|
|
|
|
Self {
|
|
|
|
username,
|
|
|
|
active_certificate,
|
|
|
|
manager,
|
|
|
|
inner,
|
|
|
|
has_changed: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-19 21:54:29 +00:00
|
|
|
/// Update the active certificate
|
2020-11-19 22:41:32 +00:00
|
|
|
///
|
|
|
|
/// This is not to be confused with [`RegisteredUser::add_certificate`], which
|
|
|
|
/// performs the database operations needed to register a new certificate to a user.
|
|
|
|
/// This literally just marks the active certificate.
|
2020-12-01 19:43:15 +00:00
|
|
|
pub (crate) fn with_cert(mut self, cert: [u8; 32]) -> Self {
|
2020-11-19 21:54:29 +00:00
|
|
|
self.active_certificate = Some(cert);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-12-01 19:43:15 +00:00
|
|
|
/// Get the fingerprint of the certificate that the user is currently using.
|
2020-11-19 21:54:29 +00:00
|
|
|
///
|
|
|
|
/// If this user was retrieved by a [`UserManager::lookup_user()`], this will be
|
|
|
|
/// [`None`]. In all other cases, this will be [`Some`].
|
2020-12-01 19:43:15 +00:00
|
|
|
pub fn active_certificate(&self) -> Option<&[u8; 32]> {
|
2020-11-19 21:54:29 +00:00
|
|
|
self.active_certificate.as_ref()
|
2020-11-16 06:13:16 +00:00
|
|
|
}
|
|
|
|
|
2020-12-01 19:43:15 +00:00
|
|
|
/// Produce a list of all certificate fingerprints registered to this account
|
|
|
|
pub fn all_certificates(&self) -> &Vec<[u8; 32]> {
|
|
|
|
&self.inner.certificates
|
2020-11-16 06:13:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the user's current username.
|
|
|
|
///
|
|
|
|
/// NOTE: This is not guaranteed not to change.
|
2020-11-19 19:25:36 +00:00
|
|
|
pub fn username(&self) -> &String {
|
2020-11-16 06:13:16 +00:00
|
|
|
&self.username
|
|
|
|
}
|
|
|
|
|
2020-11-23 03:24:36 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")]
|
2020-11-16 06:13:16 +00:00
|
|
|
/// Check a password against the user's password hash
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
/// An error is raised if the user has yet to set a password, or if the user's
|
|
|
|
/// password hash is somehow malformed.
|
|
|
|
pub fn check_password(
|
|
|
|
&self,
|
|
|
|
try_password: impl AsRef<[u8]>
|
|
|
|
) -> Result<bool> {
|
2020-11-19 21:25:13 +00:00
|
|
|
if let Some((hash, salt)) = &self.inner.pass_hash {
|
|
|
|
Ok(argon2::verify_raw(
|
|
|
|
try_password.as_ref(),
|
|
|
|
salt,
|
|
|
|
hash.as_ref(),
|
|
|
|
&ARGON2_CONFIG,
|
|
|
|
)?)
|
2020-11-16 06:13:16 +00:00
|
|
|
} else {
|
|
|
|
Err(super::UserManagerError::PasswordNotSet)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-23 03:24:36 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")]
|
2020-11-19 21:25:13 +00:00
|
|
|
/// Set's the password for this user
|
|
|
|
///
|
|
|
|
/// By default, users have no password, meaning the cannot add any certificates beyond
|
|
|
|
/// the one they created their account with. However, by setting their password,
|
|
|
|
/// users are able to add more devices to their account, and recover their account if
|
|
|
|
/// it's lost. Note that this will completely overwrite the users old password.
|
|
|
|
///
|
2020-11-19 22:00:32 +00:00
|
|
|
/// Use [`RegisteredUser::check_password()`] and [`NotSignedInUser::attach()`] to check
|
2020-11-19 21:25:13 +00:00
|
|
|
/// the password against another one, or to link a new certificate.
|
|
|
|
///
|
|
|
|
/// Because this method uses a key derivation algorithm, this should be considered a
|
|
|
|
/// very expensive operation.
|
|
|
|
pub fn set_password(
|
|
|
|
&mut self,
|
|
|
|
password: impl AsRef<[u8]>,
|
|
|
|
) -> Result<()> {
|
|
|
|
let salt: [u8; 32] = ring::rand::generate(&*RANDOM)
|
|
|
|
.expect("Error generating random salt")
|
|
|
|
.expose();
|
|
|
|
self.inner.pass_hash = Some((
|
|
|
|
argon2::hash_raw(
|
|
|
|
password.as_ref(),
|
|
|
|
salt.as_ref(),
|
|
|
|
&ARGON2_CONFIG,
|
|
|
|
)?,
|
|
|
|
salt,
|
|
|
|
));
|
2020-11-22 05:42:26 +00:00
|
|
|
self.has_changed = true;
|
2020-11-19 21:25:13 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-11-16 06:13:16 +00:00
|
|
|
/// Write any updates to the user to the database.
|
|
|
|
///
|
|
|
|
/// Updates caused by calling methods directly on the user do not need to be saved.
|
|
|
|
/// This is only for changes made to the UserData.
|
2020-11-19 19:25:36 +00:00
|
|
|
pub fn save(&mut self) -> Result<()>
|
2020-11-16 06:13:16 +00:00
|
|
|
where
|
|
|
|
UserData: Serialize
|
|
|
|
{
|
2020-11-19 19:25:36 +00:00
|
|
|
self.inner.store(&self.manager.users, &self.username)?;
|
|
|
|
self.has_changed = false;
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-11-19 22:41:32 +00:00
|
|
|
|
|
|
|
/// Register a new certificate to this user
|
|
|
|
///
|
|
|
|
/// This adds a new certificate to this user for use in logins. This requires a
|
|
|
|
/// couple database accesses, one in order to link the user to the certificate, and
|
|
|
|
/// one in order to link the certificate to the user.
|
|
|
|
///
|
|
|
|
/// If you have a [`NotSignedInUser`] and are looking for a way to link them to an
|
|
|
|
/// existing user, consider [`NotSignedInUser::attach()`], which contains facilities for
|
|
|
|
/// password checking and automatically performs the user lookup.
|
2020-12-01 19:43:15 +00:00
|
|
|
pub fn add_certificate(&mut self, certificate: [u8; 32]) -> Result<()> {
|
|
|
|
self.inner.certificates.push(certificate);
|
2020-11-19 22:41:32 +00:00
|
|
|
|
|
|
|
let inner_serialized = bincode::serialize(&self.inner)?;
|
|
|
|
|
|
|
|
(&self.manager.users, &self.manager.certificates)
|
|
|
|
.transaction(|(tx_usr, tx_crt)| {
|
|
|
|
tx_usr.insert(
|
|
|
|
self.username.as_str(),
|
|
|
|
inner_serialized.clone(),
|
|
|
|
)?;
|
|
|
|
tx_crt.insert(
|
2020-12-01 19:43:15 +00:00
|
|
|
&certificate,
|
|
|
|
self.username.as_bytes(),
|
2020-11-19 22:41:32 +00:00
|
|
|
)?;
|
|
|
|
Ok(())
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-11-22 05:33:57 +00:00
|
|
|
|
2020-11-23 03:24:36 +00:00
|
|
|
#[cfg(feature = "user_management_advanced")]
|
2020-11-22 05:33:57 +00:00
|
|
|
/// Check if the user has a password set
|
|
|
|
///
|
|
|
|
/// Since authentication is done using client certificates, users aren't required to
|
|
|
|
/// set a password up front. In some cases, it may be useful to know if a user has or
|
|
|
|
/// has not set a password yet.
|
|
|
|
///
|
|
|
|
/// This returns `true` if the user has a password set, or `false` otherwise
|
|
|
|
pub fn has_password(&self) -> bool {
|
|
|
|
self.inner.pass_hash.is_some()
|
|
|
|
}
|
2020-11-28 20:01:39 +00:00
|
|
|
|
|
|
|
/// Get an immutable reference to the data associated with this user
|
|
|
|
pub fn data(&self) -> &UserData {
|
|
|
|
&self.inner.data
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a mutable reference to the data associated with this user
|
|
|
|
///
|
|
|
|
/// This automatically flags the user data as needing to be saved to the database,
|
|
|
|
/// which automatically performs the action when this user falls out of scope. If
|
|
|
|
/// need be, you can push these changes to the database sooner by calling [`save()`]
|
|
|
|
///
|
|
|
|
/// [`save()`]: Self::save()
|
|
|
|
pub fn mut_data(&mut self) -> &mut UserData {
|
|
|
|
self.has_changed = true;
|
|
|
|
&mut self.inner.data
|
|
|
|
}
|
2020-11-19 19:25:36 +00:00
|
|
|
}
|
|
|
|
|
2020-11-19 22:00:32 +00:00
|
|
|
impl<UserData: Serialize + DeserializeOwned> std::ops::Drop for RegisteredUser<UserData> {
|
2020-11-19 19:25:36 +00:00
|
|
|
fn drop(&mut self) {
|
|
|
|
if self.has_changed {
|
|
|
|
if let Err(e) = self.save() {
|
|
|
|
error!("Failed to save user data to database: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
2020-11-16 06:13:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-19 22:00:32 +00:00
|
|
|
impl<UserData: Serialize + DeserializeOwned> AsRef<UserData> for RegisteredUser<UserData> {
|
2020-11-16 06:13:16 +00:00
|
|
|
fn as_ref(&self) -> &UserData {
|
2020-11-28 20:01:39 +00:00
|
|
|
self.data()
|
2020-11-16 06:13:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-19 22:00:32 +00:00
|
|
|
impl<UserData: Serialize + DeserializeOwned> AsMut<UserData> for RegisteredUser<UserData> {
|
2020-11-16 06:13:16 +00:00
|
|
|
fn as_mut(&mut self) -> &mut UserData {
|
2020-11-28 20:01:39 +00:00
|
|
|
self.mut_data()
|
2020-11-16 06:13:16 +00:00
|
|
|
}
|
|
|
|
}
|