isahc/
auth.rs

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