Skip to main content

rs_teststand/users/
user.rs

1//! A station user.
2
3use rs_teststand_sys::{Dispatch, Value};
4
5use crate::Error;
6use crate::dispids::user;
7use crate::property::PropertyObject;
8use crate::users::UserPrivilege;
9
10/// A user account (`User`).
11///
12/// Obtained from [`Engine::new_user`](crate::Engine::new_user),
13/// [`Engine::get_user`](crate::Engine::get_user), or
14/// [`Engine::current_user`](crate::Engine::current_user).
15///
16/// A user object built in memory is not part of the station until it is written
17/// to the users file; creating and configuring one here changes nothing on disk.
18///
19/// Effective privileges usually come from the groups a user belongs to rather
20/// than from the user directly, which is why
21/// [`has_privilege`](Self::has_privilege) answers for the user *and* their
22/// groups, while [`privileges`](Self::privileges) exposes only what is set on
23/// the user itself.
24#[derive(Debug)]
25pub struct User {
26    dispatch: Box<dyn Dispatch>,
27}
28
29impl User {
30    /// Wraps a dispatch handle returned by the engine.
31    pub(crate) fn new(dispatch: Box<dyn Dispatch>) -> Self {
32        Self { dispatch }
33    }
34
35    /// The login name (`User.LoginName`).
36    ///
37    /// # Errors
38    /// [`Error`] if the COM call fails or returns an unexpected type.
39    pub fn login_name(&self) -> Result<String, Error> {
40        Ok(self.dispatch.get(user::LOGIN_NAME)?.into_string()?)
41    }
42
43    /// Sets the login name (`User.LoginName`).
44    ///
45    /// # Errors
46    /// [`Error`] if the COM call fails.
47    pub fn set_login_name(&self, name: &str) -> Result<(), Error> {
48        self.dispatch
49            .put(user::LOGIN_NAME, Value::Str(name.to_owned()))?;
50        Ok(())
51    }
52
53    /// The display name (`User.FullName`).
54    ///
55    /// # Errors
56    /// [`Error`] if the COM call fails or returns an unexpected type.
57    pub fn full_name(&self) -> Result<String, Error> {
58        Ok(self.dispatch.get(user::FULL_NAME)?.into_string()?)
59    }
60
61    /// Sets the display name (`User.FullName`).
62    ///
63    /// # Errors
64    /// [`Error`] if the COM call fails.
65    pub fn set_full_name(&self, name: &str) -> Result<(), Error> {
66        self.dispatch
67            .put(user::FULL_NAME, Value::Str(name.to_owned()))?;
68        Ok(())
69    }
70
71    /// Sets the password (`User.Password`).
72    ///
73    /// # Errors
74    /// [`Error`] if the COM call fails.
75    pub fn set_password(&self, password: &str) -> Result<(), Error> {
76        self.dispatch
77            .put(user::PASSWORD, Value::Str(password.to_owned()))?;
78        Ok(())
79    }
80
81    /// Reads the stored password field (`User.Password`).
82    ///
83    /// Prefer [`validate_password`](Self::validate_password) for checking a
84    /// credential: it compares without the caller handling the stored value.
85    ///
86    /// # Errors
87    /// [`Error`] if the COM call fails or returns an unexpected type.
88    pub fn password(&self) -> Result<String, Error> {
89        Ok(self.dispatch.get(user::PASSWORD)?.into_string()?)
90    }
91
92    /// Whether `password` matches this user's (`User.ValidatePassword`).
93    ///
94    /// # Errors
95    /// [`Error`] if the COM call fails or returns an unexpected type.
96    pub fn validate_password(&self, password: &str) -> Result<bool, Error> {
97        Ok(self
98            .dispatch
99            .call(user::VALIDATE_PASSWORD, &[Value::Str(password.to_owned())])?
100            .as_bool()?)
101    }
102
103    /// Whether the user, or any group they belong to, holds a privilege
104    /// (`User.HasPrivilege`).
105    ///
106    /// # Errors
107    /// [`Error`] if the COM call fails or returns an unexpected type.
108    pub fn has_privilege(&self, privilege: UserPrivilege) -> Result<bool, Error> {
109        self.has_privilege_named(privilege.name())
110    }
111
112    /// Whether the user holds a privilege named by string
113    /// (`User.HasPrivilege`).
114    ///
115    /// Takes either a base name or a full path such as
116    /// `Debug.RunSelectedTests`. Prefer
117    /// [`has_privilege`](Self::has_privilege) for the built-in set; this exists
118    /// for custom privileges, which no enum can enumerate.
119    ///
120    /// # Errors
121    /// [`Error`] if the COM call fails or returns an unexpected type.
122    pub fn has_privilege_named(&self, privilege: &str) -> Result<bool, Error> {
123        Ok(self
124            .dispatch
125            .call(user::HAS_PRIVILEGE, &[Value::Str(privilege.to_owned())])?
126            .as_bool()?)
127    }
128
129    /// The privilege settings held on the user itself (`User.Privileges`).
130    ///
131    /// This is not the answer to "can this user do X", group membership is not
132    /// reflected here. Use [`has_privilege`](Self::has_privilege) for that.
133    ///
134    /// # Errors
135    /// [`Error`] if the COM call fails or returns an unexpected type.
136    pub fn privileges(&self) -> Result<PropertyObject, Error> {
137        Ok(PropertyObject::new(
138            self.dispatch.get(user::PRIVILEGES)?.into_object()?,
139        ))
140    }
141
142    /// The member list of a user *group* (`User.Members`).
143    ///
144    /// Only meaningful when this object represents a group rather than a person.
145    ///
146    /// # Errors
147    /// [`Error`] if the COM call fails or returns an unexpected type.
148    pub fn members(&self) -> Result<PropertyObject, Error> {
149        Ok(PropertyObject::new(
150            self.dispatch.get(user::MEMBERS)?.into_object()?,
151        ))
152    }
153
154    /// The user as a plain property tree (`User.AsPropertyObject`).
155    ///
156    /// # Errors
157    /// [`Error`] if the COM call fails or returns an unexpected type.
158    pub fn as_property_object(&self) -> Result<PropertyObject, Error> {
159        Ok(PropertyObject::new(
160            self.dispatch
161                .call(user::AS_PROPERTY_OBJECT, &[])?
162                .into_object()?,
163        ))
164    }
165
166    /// An owned handle to the same user, for passing it back to the engine.
167    pub(crate) fn duplicate_dispatch(&self) -> Option<Box<dyn Dispatch>> {
168        self.dispatch.duplicate()
169    }
170}