Skip to main content

isahc/
auth.rs

1//! Types for working with HTTP authentication methods.
2
3use crate::config::setopt::{EasyHandle, SetOpt, SetOptError, SetOptProxy};
4use std::{
5    fmt,
6    ops::{BitOr, BitOrAssign},
7};
8
9#[cfg(all(windows, feature = "spnego", not(feature = "native-tls")))]
10compile_error!("SPNEGO requires native-tls on Windows");
11
12/// Credentials consisting of a username and a secret (password) that can be
13/// used to establish user identity.
14#[derive(Clone)]
15pub struct Credentials {
16    username: String,
17    password: String,
18}
19
20impl Credentials {
21    /// Create credentials from a username and password.
22    pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
23        Self {
24            username: username.into(),
25            password: password.into(),
26        }
27    }
28}
29
30impl SetOpt for Credentials {
31    fn set_opt(&self, easy: &mut EasyHandle) -> Result<(), SetOptError> {
32        easy.username(&self.username)?;
33        easy.password(&self.password)?;
34        Ok(())
35    }
36}
37
38impl SetOptProxy for Credentials {
39    fn set_opt_proxy(&self, easy: &mut EasyHandle) -> Result<(), SetOptError> {
40        easy.proxy_username(&self.username)?;
41        easy.proxy_password(&self.password)?;
42        Ok(())
43    }
44}
45
46// Implement our own debug since we don't want to print passwords even on
47// accident.
48impl fmt::Debug for Credentials {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        f.debug_struct("Credentials")
51            .field("username", &self.username)
52            .field("password", &"*****")
53            .finish()
54    }
55}
56
57/// Specifies one or more HTTP authentication schemes to use.
58#[derive(Clone, Debug)]
59pub struct Authentication(u8);
60
61impl Default for Authentication {
62    fn default() -> Self {
63        Self::none()
64    }
65}
66
67impl Authentication {
68    /// Disable all authentication schemes. This is the default.
69    pub const fn none() -> Self {
70        Authentication(0)
71    }
72
73    /// Enable all available authentication schemes.
74    pub const fn all() -> Self {
75        #[allow(unused_mut)]
76        let mut all = Self::basic().0 | Self::digest().0;
77
78        #[cfg(feature = "spnego")]
79        {
80            all |= Self::negotiate().0;
81        }
82
83        Authentication(all)
84    }
85
86    /// HTTP Basic authentication.
87    ///
88    /// This authentication scheme sends the user name and password over the
89    /// network in plain text. Avoid using this scheme without TLS as the
90    /// credentials can be easily captured otherwise.
91    pub const fn basic() -> Self {
92        Authentication(0b0001)
93    }
94
95    /// HTTP Digest authentication.
96    ///
97    /// Digest authentication is defined in RFC 2617 and is a more secure way to
98    /// do authentication over public networks than the regular old-fashioned
99    /// Basic method.
100    pub const fn digest() -> Self {
101        Authentication(0b0010)
102    }
103
104    /// HTTP Negotiate (SPNEGO) authentication.
105    ///
106    /// Negotiate authentication is defined in RFC 4559 and is the most secure
107    /// way to perform authentication over HTTP. Specifying [`Credentials`] is
108    /// not necessary as credentials are provided by platform authentication
109    /// means.
110    ///
111    /// You need to build libcurl with a suitable GSS-API library or SSPI on
112    /// Windows for this to work. This is automatic when binding to curl
113    /// statically, otherwise it depends on how your system curl is configured.
114    ///
115    /// # Availability
116    ///
117    /// This method is only available when the [`spnego`](../index.html#spnego)
118    /// feature is enabled.
119    #[cfg(feature = "spnego")]
120    pub const fn negotiate() -> Self {
121        Authentication(0b0100)
122    }
123
124    const fn contains(&self, other: Self) -> bool {
125        (self.0 & other.0) == other.0
126    }
127
128    fn as_auth(&self) -> curl::easy::Auth {
129        let mut auth = curl::easy::Auth::new();
130
131        if self.contains(Authentication::basic()) {
132            auth.basic(true);
133        }
134
135        if self.contains(Authentication::digest()) {
136            auth.digest(true);
137        }
138
139        #[cfg(feature = "spnego")]
140        {
141            if self.contains(Authentication::negotiate()) {
142                auth.gssnegotiate(true);
143            }
144        }
145
146        auth
147    }
148}
149
150impl BitOr for Authentication {
151    type Output = Self;
152
153    fn bitor(mut self, other: Self) -> Self {
154        self |= other;
155        self
156    }
157}
158
159impl BitOrAssign for Authentication {
160    fn bitor_assign(&mut self, rhs: Self) {
161        self.0 |= rhs.0;
162    }
163}
164
165impl SetOpt for Authentication {
166    fn set_opt(&self, easy: &mut EasyHandle) -> Result<(), SetOptError> {
167        #[cfg(feature = "spnego")]
168        {
169            if self.contains(Authentication::negotiate()) {
170                // Ensure auth engine is enabled, even though credentials do not
171                // need to be specified.
172                easy.username("")?;
173                easy.password("")?;
174            }
175        }
176
177        easy.http_auth(&self.as_auth())?;
178
179        Ok(())
180    }
181}
182
183impl SetOptProxy for Authentication {
184    fn set_opt_proxy(&self, easy: &mut EasyHandle) -> Result<(), SetOptError> {
185        #[cfg(feature = "spnego")]
186        {
187            if self.contains(Authentication::negotiate()) {
188                // Ensure auth engine is enabled, even though credentials do not
189                // need to be specified.
190                easy.proxy_username("")?;
191                easy.proxy_password("")?;
192            }
193        }
194
195        easy.proxy_auth(&self.as_auth())?;
196
197        Ok(())
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::Authentication;
204
205    #[test]
206    fn auth_default() {
207        let auth = Authentication::default();
208
209        assert!(!auth.contains(Authentication::basic()));
210        assert!(!auth.contains(Authentication::digest()));
211    }
212
213    #[test]
214    fn auth_all() {
215        let auth = Authentication::all();
216
217        assert!(auth.contains(Authentication::basic()));
218        assert!(auth.contains(Authentication::digest()));
219    }
220
221    #[test]
222    fn auth_single() {
223        let auth = Authentication::basic();
224
225        assert!(auth.contains(Authentication::basic()));
226        assert!(!auth.contains(Authentication::digest()));
227
228        let auth = Authentication::digest();
229
230        assert!(!auth.contains(Authentication::basic()));
231        assert!(auth.contains(Authentication::digest()));
232    }
233}