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