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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
//! # authentic
//!
//! Handle authentication of HTTP calls.
//!
//! Authentication protocols can require specific workflows, such as making third-party calls to refresh a token or performing an initial request to get challenge information.
//!
//! Using a fixed code structure, `authentic` can perform the necessary interactions for each authentication protocol. This allows protocols to be changed easily.
//!
//! For example, the following code uses `reqwest` to access a site using HTTP Basic authentication. (See the [repository tests directory](https://github.com/jinxapi/authentic/tree/main/tests) for fully working examples).
//!
//! ```ignore
//! // One-time code:
//! let client = reqwest::blocking::Client::new();
//!
//! let mut realm_credentials = HashMap::new();
//! realm_credentials.insert(
//! "Fake Realm".into(),
//! Arc::new(UsernamePasswordCredential::new("username", "password")),
//! );
//! let credential = Arc::new(HttpRealmCredentials::new(realm_credentials));
//!
//! // Per-request code:
//! let mut authentication = HttpAuthentication::new(credential.clone());
//!
//! let response = loop {
//! while let Some(auth_step) = authentication.step()? {
//! match auth_step {
//! AuthenticationStep::Request(request) => {
//! let auth_response = client.execute(request);
//! authentication.respond(auth_response);
//! }
//! AuthenticationStep::WaitFor(duration) => {
//! std::thread::sleep(duration);
//! }
//! }
//! }
//!
//! let response = client
//! .get("https://httpbin.org/basic-auth/username/password")
//! .with_authentication(&authentication)?
//! .send()?;
//!
//! if authentication.has_completed(&response)? {
//! break response;
//! }
//! };
//! ```
//!
//! The creation of the request takes place inside a loop. First, the authentication protocol is given an opportunity to perform any third-party calls using `step()`.
//! HTTP Basic authentication does not use this, but it can be used, for example, to refresh an expired OAuth2 access token.
//!
//! The request is created using a standard `reqwest::RequestBuilder`, using a new `with_authentication()` method to modify the request for the authentication protocol.
//! For HTTP authentication, the first iteration makes no change to the request.
//!
//! The request is sent and a response is received. For HTTP authentication, this returns a `401 Unauthorized` response.
//!
//! The `has_completed()` method checks if the response is ready to be returned or if the authentication protocol needs to retry.
//! For HTTP authentication, this reads the returned `www-authenticate` challenge and establishes the correct credentials.
//! As the request needs to be retried, `has_completed()` returns `false` and a second iteration begins.
//!
//! On the second iteration of the loop, `with_authentication()` adds the credentials as the `Authorization` header to the request. The request is authenticated and the response contains the correct data. `has_completed()` will return `true` and the loop exits with the response.
//!
//! ## HTTP library features
//!
//! `authentic` supports asynchronous code using `hyper` or `reqwest`, and blocking code using `reqwest`.
//!
//! Specify the library using the following features:
//! - `hyper-client`
//! - `reqwest-async`
//! - `reqwest-blocking`
//!
//! ## Algorithm features
//!
//! The above per-request code works for all supported authentication methods, but requires both the `step` and `loop` features to be enabled.
//!
//! If the `loop` feature is not enabled, then the outer loop is not required.
//!
//! If the `step` feature is not enabled, then the inner `while` loop is not required.
//!
//! For example, a JWT credential requires the `step` feature, but does not require the `loop` feature.
//! Hence the code can remove the outer loop.
//! (Note that the JWT credential also requires the `jwt` feature.)
//!
//! ```ignore
//! // One-time code:
//! let client = reqwest::blocking::Client::new();
//!
//! let credential = Arc::new(JsonWebTokenCredential::new(
//! jsonwebtoken::Header::new(jsonwebtoken::Algorithm::RS256),
//! jsonwebtoken::EncodingKey::from_rsa_pem(private_key)?,
//! Duration::from_secs(10 * 60),
//! ));
//!
//! // Per-request code:
//! let mut authentication = BearerAuthentication::new(credential.clone());
//!
//! while let Some(auth_step) = authentication.step()? {
//! match auth_step {
//! AuthenticationStep::Request(request) => {
//! let auth_response = client.execute(request);
//! authentication.respond(auth_response);
//! }
//! AuthenticationStep::WaitFor(duration) => {
//! std::thread::sleep(duration);
//! }
//! }
//! }
//!
//! let response = client
//! .get(url)
//! .with_authentication(&authentication)?
//! .send()?;
//! ```
//!
//! If an API always requires basic authentication with specific credentials, neither the `step` or `loop` features are required:
//!
//! ```ignore
//! // One-time code:
//! let client = reqwest::blocking::Client::new();
//!
//! let credential = Arc::new(UsernamePasswordCredential::new("username", "password"));
//!
//! // Per-request code:
//! let mut authentication = BasicAuthentication::new(credential.clone());
//!
//! let response = client
//! .get("https://httpbin.org/basic-auth/username/password")
//! .with_authentication(&authentication)?
//! .send()?;
//! ```
//!
//! ## Supported combinations
//!
//! The supported algorithm-credential pairs, and the features required to enable them, are:
//! - `NoAuthentication`
//! - `BasicAuthentication<UsernamePasswordCredential>`
//! - `BearerAuthentication<JsonWebTokenCredential>` (`features = ["jwt", "step"]`)
//! - `BearerAuthentication<TokenCredential>`
//! - `HeaderAuthentication<JsonWebTokenCredential>` (`features = ["jwt", "step"]`)
//! - `HeaderAuthentication<TokenCredential>`
//! - `HttpAuthentication<HttpRealmCredentials<UsernamePasswordCredential>>` (`features = ["loop"]`)
//!
use Duration;
use Error;
// Allow request builder authentication to use fluent model.