nethsm/base/
impl_base.rs

1//! Base implementation for [`NetHsm`]
2
3#[cfg(doc)]
4use std::thread::available_parallelism;
5use std::{cell::RefCell, collections::HashMap};
6
7use log::{debug, trace};
8use nethsm_sdk_rs::apis::configuration::Configuration;
9#[cfg(doc)]
10use ureq::Agent;
11
12use crate::{
13    Connection,
14    ConnectionSecurity,
15    Credentials,
16    DEFAULT_MAX_IDLE_CONNECTIONS,
17    DEFAULT_TIMEOUT_SECONDS,
18    Error,
19    NetHsm,
20    Url,
21    UserId,
22    UserRole,
23    tls::create_agent,
24    user::NamespaceSupport,
25};
26
27impl NetHsm {
28    /// Creates a new NetHSM connection.
29    ///
30    /// Creates a new NetHSM connection based on a [`Connection`].
31    ///
32    /// Optionally initial `credentials` (used when communicating with the NetHSM),
33    /// `max_idle_connections` to set the size of the connection pool (defaults to `100`) and
34    /// `timeout_seconds` to set the timeout for a successful socket connection (defaults to `10`)
35    /// can be provided.
36    ///
37    /// # Errors
38    ///
39    /// - the TLS client configuration can not be created,
40    /// - or [`ConnectionSecurity::Native`] is provided as `tls_security`, but no certification
41    ///   authority certificates are available on the system.
42    pub fn new(
43        connection: Connection,
44        credentials: Option<Credentials>,
45        max_idle_connections: Option<usize>,
46        timeout_seconds: Option<u64>,
47    ) -> Result<Self, Error> {
48        let (current_credentials, credentials) = if let Some(credentials) = credentials {
49            debug!(
50                "Create new NetHSM connection {connection} with initial credentials {credentials}"
51            );
52            (
53                RefCell::new(Some(credentials.user_id.clone())),
54                RefCell::new(HashMap::from([(credentials.user_id.clone(), credentials)])),
55            )
56        } else {
57            debug!("Create new NetHSM connection {connection} with no initial credentials");
58            (Default::default(), Default::default())
59        };
60
61        let agent = RefCell::new(create_agent(
62            connection.tls_security,
63            max_idle_connections,
64            timeout_seconds,
65        )?);
66
67        Ok(Self {
68            agent,
69            url: RefCell::new(connection.url),
70            current_credentials,
71            credentials,
72        })
73    }
74
75    /// Validates the potential [namespace] access of a context.
76    ///
77    /// Validates, that [`current_credentials`][`NetHsm::current_credentials`] can be used in a
78    /// defined context. This function relies on [`UserId::validate_namespace_access`] and should be
79    /// used for validating the context of [`NetHsm`] methods.
80    ///
81    /// [namespace]: https://docs.nitrokey.com/nethsm/administration#namespaces
82    pub(crate) fn validate_namespace_access(
83        &self,
84        support: NamespaceSupport,
85        target: Option<&UserId>,
86        role: Option<&UserRole>,
87    ) -> Result<(), Error> {
88        debug!(
89            "Validate namespace access (target: {}; namespace: {support}; role: {}) for NetHSM at {}",
90            if let Some(target) = target {
91                target.to_string()
92            } else {
93                "n/a".to_string()
94            },
95            if let Some(role) = role {
96                role.to_string()
97            } else {
98                "n/a".to_string()
99            },
100            self.url.borrow()
101        );
102
103        if let Some(current_user_id) = self.current_credentials.borrow().to_owned() {
104            current_user_id.validate_namespace_access(support, target, role)?
105        }
106        Ok(())
107    }
108
109    /// Creates a connection configuration.
110    ///
111    /// Uses the [`Agent`] configured during creation of the [`NetHsm`], the current [`Url`] and
112    /// [`Credentials`] to create a [`Configuration`] for a connection to the API of a NetHSM.
113    pub(crate) fn create_connection_config(&self) -> Configuration {
114        debug!(
115            "Create connection config for NetHSM at {}",
116            self.url.borrow()
117        );
118
119        let current_credentials = self.current_credentials.borrow().to_owned();
120        Configuration {
121            client: self.agent.borrow().to_owned(),
122            base_path: self.url.borrow().to_string(),
123            basic_auth: if let Some(current_credentials) = current_credentials {
124                self.credentials
125                    .borrow()
126                    .get(&current_credentials)
127                    .map(Into::into)
128            } else {
129                None
130            },
131            user_agent: Some(format!(
132                "{}/{}",
133                env!("CARGO_PKG_NAME"),
134                env!("CARGO_PKG_VERSION")
135            )),
136            ..Default::default()
137        }
138    }
139
140    /// Sets the connection agent for the NetHSM connection.
141    ///
142    /// Allows setting the
143    /// - [`ConnectionSecurity`] which defines the TLS security model for the connection,
144    /// - maximum idle connections per host using the optional `max_idle_connections` (defaults to
145    ///   [`available_parallelism`] and falls back to `100` if unavailable),
146    /// - and timeout in seconds for a successful socket connection using the optional
147    ///   `timeout_seconds` (defaults to `10`).
148    ///
149    /// # Errors
150    ///
151    /// Returns an error if
152    ///
153    /// - the TLS client configuration can not be created,
154    /// - [`ConnectionSecurity::Native`] is provided as `tls_security`, but no certification
155    ///   authority certificates are available on the system.
156    ///
157    /// # Examples
158    ///
159    /// ```
160    /// use nethsm::{Connection, ConnectionSecurity, NetHsm, Url};
161    ///
162    /// # fn main() -> testresult::TestResult {
163    /// // Create a new connection for a NetHSM at "https://example.org"
164    /// let nethsm = NetHsm::new(
165    ///     Connection::new(
166    ///         "https://example.org/api/v1".try_into()?,
167    ///         ConnectionSecurity::Unsafe,
168    ///     ),
169    ///     None,
170    ///     None,
171    ///     None,
172    /// )?;
173    ///
174    /// // change the connection agent to something else
175    /// nethsm.set_agent(ConnectionSecurity::Unsafe, Some(200), Some(30))?;
176    /// # Ok(())
177    /// # }
178    /// ```
179    pub fn set_agent(
180        &self,
181        tls_security: ConnectionSecurity,
182        max_idle_connections: Option<usize>,
183        timeout_seconds: Option<u64>,
184    ) -> Result<(), Error> {
185        debug!(
186            "Set TLS agent (TLS security: {tls_security}; max idle: {}, timeout: {}) for NetHSM at {}",
187            if let Some(max_idle_connections) = max_idle_connections {
188                max_idle_connections.to_string()
189            } else {
190                DEFAULT_MAX_IDLE_CONNECTIONS.to_string()
191            },
192            if let Some(timeout_seconds) = timeout_seconds {
193                format!("{timeout_seconds}s")
194            } else {
195                format!("{DEFAULT_TIMEOUT_SECONDS}s")
196            },
197            self.url.borrow()
198        );
199
200        *self.agent.borrow_mut() =
201            create_agent(tls_security, max_idle_connections, timeout_seconds)?;
202        Ok(())
203    }
204
205    /// Sets the URL for the NetHSM connection.
206    ///
207    /// # Examples
208    ///
209    /// ```
210    /// use nethsm::{Connection, ConnectionSecurity, NetHsm, Url};
211    ///
212    /// # fn main() -> testresult::TestResult {
213    /// // Create a new connection for a NetHSM at "https://example.org"
214    /// let nethsm = NetHsm::new(
215    ///     Connection::new(
216    ///         "https://example.org/api/v1".try_into()?,
217    ///         ConnectionSecurity::Unsafe,
218    ///     ),
219    ///     None,
220    ///     None,
221    ///     None,
222    /// )?;
223    ///
224    /// // change the url to something else
225    /// nethsm.set_url(Url::new("https://other.org/api/v1")?);
226    /// # Ok(())
227    /// # }
228    /// ```
229    pub fn set_url(&self, url: Url) {
230        debug!(
231            "Set the URL to {url} for the NetHSM at {}",
232            self.url.borrow()
233        );
234
235        *self.url.borrow_mut() = url;
236    }
237
238    /// Retrieves the current URL for the NetHSM connection.
239    ///
240    /// # Examples
241    ///
242    /// ```
243    /// use nethsm::{Connection, ConnectionSecurity, NetHsm, Url};
244    ///
245    /// # fn main() -> testresult::TestResult {
246    /// // Create a new connection for a NetHSM at "https://example.org"
247    /// let nethsm = NetHsm::new(
248    ///     Connection::new(
249    ///         "https://example.org/api/v1".try_into()?,
250    ///         ConnectionSecurity::Unsafe,
251    ///     ),
252    ///     None,
253    ///     None,
254    ///     None,
255    /// )?;
256    ///
257    /// // retrieve the current URL
258    /// assert_eq!(nethsm.get_url(), "https://example.org/api/v1".try_into()?);
259    /// # Ok(())
260    /// # }
261    /// ```
262    pub fn get_url(&self) -> Url {
263        trace!("Get the URL for the NetHSM at {}", self.url.borrow());
264
265        self.url.borrow().clone()
266    }
267
268    /// Adds [`Credentials`] to the list of available ones.
269    ///
270    /// # Examples
271    ///
272    /// ```
273    /// use nethsm::{Connection, ConnectionSecurity, Credentials, NetHsm, Passphrase};
274    ///
275    /// # fn main() -> testresult::TestResult {
276    /// let nethsm = NetHsm::new(
277    ///     Connection::new(
278    ///         "https://example.org/api/v1".try_into()?,
279    ///         ConnectionSecurity::Unsafe,
280    ///     ),
281    ///     None,
282    ///     None,
283    ///     None,
284    /// )?;
285    ///
286    /// // add credentials
287    /// nethsm.add_credentials(Credentials::new(
288    ///     "admin".parse()?,
289    ///     Some(Passphrase::new("passphrase".to_string())),
290    /// ));
291    /// nethsm.add_credentials(Credentials::new(
292    ///     "user1".parse()?,
293    ///     Some(Passphrase::new("other_passphrase".to_string())),
294    /// ));
295    /// nethsm.add_credentials(Credentials::new("user2".parse()?, None));
296    /// # Ok(())
297    /// # }
298    /// ```
299    pub fn add_credentials(&self, credentials: Credentials) {
300        debug!("Add NetHSM connection credentials for {credentials}");
301
302        self.credentials
303            .borrow_mut()
304            .insert(credentials.user_id.clone(), credentials);
305    }
306
307    /// Removes [`Credentials`] from the list of available and currently used ones.
308    ///
309    /// Removes [`Credentials`] from the list of available ones and if identical unsets the
310    /// ones used for further authentication as well.
311    ///
312    /// # Examples
313    ///
314    /// ```
315    /// use nethsm::{Connection, ConnectionSecurity, Credentials, NetHsm, Passphrase};
316    ///
317    /// # fn main() -> testresult::TestResult {
318    /// let nethsm = NetHsm::new(
319    ///     Connection::new(
320    ///         "https://example.org/api/v1".try_into()?,
321    ///         ConnectionSecurity::Unsafe,
322    ///     ),
323    ///     Some(Credentials::new(
324    ///         "admin".parse()?,
325    ///         Some(Passphrase::new("passphrase".to_string())),
326    ///     )),
327    ///     None,
328    ///     None,
329    /// )?;
330    ///
331    /// // remove credentials
332    /// nethsm.remove_credentials(&"admin".parse()?);
333    /// # Ok(())
334    /// # }
335    /// ```
336    pub fn remove_credentials(&self, user_id: &UserId) {
337        debug!("Remove NetHSM connection credentials for {user_id}");
338
339        self.credentials.borrow_mut().remove(user_id);
340        if self
341            .current_credentials
342            .borrow()
343            .as_ref()
344            .is_some_and(|id| id == user_id)
345        {
346            *self.current_credentials.borrow_mut() = None
347        }
348    }
349
350    /// Sets [`Credentials`] to use for the next connection.
351    ///
352    /// # Errors
353    ///
354    /// An [`Error`] is returned if no [`Credentials`] with the [`UserId`] `user_id` can be found.
355    ///
356    /// # Examples
357    ///
358    /// ```
359    /// use nethsm::{Connection, ConnectionSecurity, Credentials, NetHsm, Passphrase};
360    ///
361    /// # fn main() -> testresult::TestResult {
362    /// let nethsm = NetHsm::new(
363    ///     Connection::new(
364    ///         "https://example.org/api/v1".try_into()?,
365    ///         ConnectionSecurity::Unsafe,
366    ///     ),
367    ///     None,
368    ///     None,
369    ///     None,
370    /// )?;
371    ///
372    /// // add credentials
373    /// nethsm.add_credentials(Credentials::new(
374    ///     "admin".parse()?,
375    ///     Some(Passphrase::new("passphrase".to_string())),
376    /// ));
377    /// nethsm.add_credentials(Credentials::new(
378    ///     "user1".parse()?,
379    ///     Some(Passphrase::new("other_passphrase".to_string())),
380    /// ));
381    ///
382    /// // use admin credentials
383    /// nethsm.use_credentials(&"admin".parse()?)?;
384    ///
385    /// // use operator credentials
386    /// nethsm.use_credentials(&"user1".parse()?)?;
387    ///
388    /// // this fails, because the user has not been added yet
389    /// assert!(nethsm.use_credentials(&"user2".parse()?).is_err());
390    /// # Ok(())
391    /// # }
392    /// ```
393    pub fn use_credentials(&self, user_id: &UserId) -> Result<(), Error> {
394        debug!("Use NetHSM connection credentials of {user_id}");
395
396        if self.credentials.borrow().contains_key(user_id) {
397            if self.current_credentials.borrow().as_ref().is_none()
398                || self
399                    .current_credentials
400                    .borrow()
401                    .as_ref()
402                    .is_some_and(|id| id != user_id)
403            {
404                *self.current_credentials.borrow_mut() = Some(user_id.to_owned());
405            }
406        } else {
407            return Err(Error::Default(format!(
408                "The credentials for User ID \"{user_id}\" need to be added before they can be used!"
409            )));
410        }
411        Ok(())
412    }
413
414    /// Get the [`UserId`] of the currently used [`Credentials`] for the connection.
415    ///
416    /// # Examples
417    ///
418    /// ```
419    /// use nethsm::{Connection, ConnectionSecurity, Credentials, NetHsm};
420    ///
421    /// # fn main() -> testresult::TestResult {
422    /// let nethsm = NetHsm::new(
423    ///     Connection::new(
424    ///         "https://example.org/api/v1".try_into()?,
425    ///         ConnectionSecurity::Unsafe,
426    ///     ),
427    ///     Some(Credentials::new(
428    ///         "admin".parse()?,
429    ///         Some("passphrase".parse()?),
430    ///     )),
431    ///     None,
432    ///     None,
433    /// )?;
434    ///
435    /// // Get current User ID
436    /// assert_eq!(nethsm.get_current_user(), Some("admin".parse()?));
437    /// # Ok(())
438    /// # }
439    /// ```
440    pub fn get_current_user(&self) -> Option<UserId> {
441        trace!(
442            "Get current User ID of NetHSM connection at {}",
443            self.url.borrow()
444        );
445
446        self.current_credentials.borrow().clone()
447    }
448}