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(¤t_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}