libdav/auth.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
// Copyright 2023-2024 Hugo Osvaldo Barrera
//
// SPDX-License-Identifier: EUPL-1.2
//! Authentication-related types.
use base64::{prelude::BASE64_STANDARD, write::EncoderWriter};
use core::fmt;
use http::{HeaderValue, Request};
use std::io::Write;
use crate::dav::RequestError;
/// Wrapper around a [`String`] that is not printed when debugging.
///
/// # Examples
///
/// ```
/// # use libdav::auth::Password;
/// let p1 = Password::from("secret");
/// let p2 = String::from("secret").into();
///
/// assert_eq!(p1, p2);
/// ```
///
/// # Display
///
/// The [`core::fmt::Display`] trait is intentionally not implemented. Use either
/// [`Password::into_string`] or [`Password::as_str()`].
#[derive(Clone, PartialEq, Eq)]
pub struct Password(String);
impl fmt::Debug for Password {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("<REDACTED>")
}
}
impl<S> From<S> for Password
where
String: From<S>,
{
fn from(value: S) -> Self {
Password(String::from(value))
}
}
#[allow(clippy::from_over_into)] // `From<Password> for String` is not feasible.
impl Into<String> for Password {
/// Returns the underlying string.
fn into(self) -> String {
self.0
}
}
impl Password {
/// Returns the underlying string.
#[must_use]
pub fn into_string(self) -> String {
self.0
}
/// Returns a reference to the underlying string.
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
/// Authentication schemes supported by [`WebDavClient`](crate::dav::WebDavClient).
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum Auth {
None,
Basic {
username: String,
password: Option<Password>,
},
}
impl Auth {
/// Apply this authentication to a request.
pub(crate) fn apply<B>(&self, mut request: Request<B>) -> Result<Request<B>, RequestError> {
// TODO: this will need to be async for things like Digest auth or OAuth.
match self {
Auth::None => Ok(request),
Auth::Basic { username, password } => {
let mut sequence = b"Basic ".to_vec();
let mut encoder = EncoderWriter::new(sequence, &BASE64_STANDARD);
if let Some(pwd) = password {
write!(encoder, "{username}:{}", pwd.0)?;
} else {
write!(encoder, "{username}:")?;
}
sequence = encoder.finish()?;
let mut header = HeaderValue::from_bytes(&sequence)
.expect("base64 string contains only ascii characters");
header.set_sensitive(true);
request
.headers_mut()
.insert(hyper::header::AUTHORIZATION, header);
Ok(request)
}
}
}
}