1use oauth2_client::{
2 device_authorization_grant::provider_ext::{
3 AccessTokenResponseErrorBody, AccessTokenResponseSuccessfulBody,
4 BodyWithDeviceAuthorizationGrant, DeviceAccessTokenEndpointRetryReason,
5 DeviceAuthorizationResponseErrorBody, DeviceAuthorizationResponseSuccessfulBody,
6 },
7 oauth2_core::{
8 device_authorization_grant::device_authorization_response::{
9 DeviceCode, UserCode, VerificationUri, VerificationUriComplete,
10 },
11 re_exports::AccessTokenResponseErrorBodyError,
12 },
13 re_exports::{
14 serde_json, thiserror, Body, ClientId, ClientSecret, Deserialize, Map, Response,
15 SerdeJsonError, Serialize, Url, UrlParseError, Value,
16 },
17 Provider, ProviderExtDeviceAuthorizationGrant,
18};
19
20use crate::{FacebookScope, DEVICE_AUTHORIZATION_URL, DEVICE_TOKEN_URL};
21
22#[derive(Debug, Clone)]
23pub struct FacebookProviderForDevices {
24 client_access_token: String,
25 pub redirect_uri: Option<String>,
26 token_endpoint_url: Url,
28 device_authorization_endpoint_url: Url,
29}
30impl FacebookProviderForDevices {
31 pub fn new(app_id: String, client_token: String) -> Result<Self, UrlParseError> {
32 Ok(Self {
33 client_access_token: format!("{app_id}|{client_token}"),
34 redirect_uri: None,
35 token_endpoint_url: DEVICE_TOKEN_URL.parse()?,
36 device_authorization_endpoint_url: DEVICE_AUTHORIZATION_URL.parse()?,
37 })
38 }
39
40 pub fn configure<F>(mut self, mut f: F) -> Self
41 where
42 F: FnMut(&mut Self),
43 {
44 f(&mut self);
45 self
46 }
47}
48impl Provider for FacebookProviderForDevices {
49 type Scope = FacebookScope;
50
51 fn client_id(&self) -> Option<&ClientId> {
52 None
53 }
54
55 fn client_secret(&self) -> Option<&ClientSecret> {
56 None
57 }
58
59 fn token_endpoint_url(&self) -> &Url {
60 &self.token_endpoint_url
61 }
62}
63impl ProviderExtDeviceAuthorizationGrant for FacebookProviderForDevices {
64 fn device_authorization_endpoint_url(&self) -> &Url {
65 &self.device_authorization_endpoint_url
66 }
67
68 fn device_authorization_request_body_extra(&self) -> Option<Map<String, Value>> {
69 let mut map = Map::new();
70
71 map.insert(
72 "access_token".to_owned(),
73 Value::String(self.client_access_token.to_owned()),
74 );
75
76 if let Some(redirect_uri) = &self.redirect_uri {
77 map.insert(
78 "redirect_uri".to_owned(),
79 Value::String(redirect_uri.to_owned()),
80 );
81 }
82
83 Some(map)
84 }
85
86 fn device_authorization_response_parsing(
87 &self,
88 response: &Response<Body>,
89 ) -> Option<
90 Result<
91 Result<DeviceAuthorizationResponseSuccessfulBody, DeviceAuthorizationResponseErrorBody>,
92 Box<dyn std::error::Error + Send + Sync + 'static>,
93 >,
94 > {
95 fn doing(
96 response: &Response<Body>,
97 ) -> Result<
98 Result<
99 FacebookDeviceAuthorizationResponseSuccessfulBody,
100 FacebookDeviceAuthorizationResponseErrorBody,
101 >,
102 Box<dyn std::error::Error + Send + Sync + 'static>,
103 > {
104 if response.status().is_success() {
105 let map = serde_json::from_slice::<Map<String, Value>>(response.body())
106 .map_err(DeviceAuthorizationResponseParsingError::DeResponseBodyFailed)?;
107 if !map.contains_key("error") {
108 let body = serde_json::from_slice::<
109 FacebookDeviceAuthorizationResponseSuccessfulBody,
110 >(response.body())
111 .map_err(DeviceAuthorizationResponseParsingError::DeResponseBodyFailed)?;
112
113 return Ok(Ok(body));
114 }
115 }
116
117 let body = serde_json::from_slice::<FacebookDeviceAuthorizationResponseErrorBody>(
118 response.body(),
119 )
120 .map_err(DeviceAuthorizationResponseParsingError::DeResponseBodyFailed)?;
121 Ok(Err(body))
122 }
123
124 match doing(response) {
125 Ok(Ok(ok_body)) => Some(Ok(Ok(ok_body.into()))),
126 Ok(Err(err_body)) => match DeviceAuthorizationResponseErrorBody::try_from(err_body) {
127 Ok(err_body) => Some(Ok(Err(err_body))),
128 Err(err) => Some(Err(err)),
129 },
130 Err(err) => Some(Err(err)),
131 }
132 }
133
134 fn device_access_token_request_body_extra(
135 &self,
136 body: &BodyWithDeviceAuthorizationGrant,
137 _device_authorization_response_body: &DeviceAuthorizationResponseSuccessfulBody,
138 ) -> Option<Map<String, Value>> {
139 let mut map = Map::new();
140
141 map.insert(
142 "access_token".to_owned(),
143 Value::String(self.client_access_token.to_owned()),
144 );
145
146 map.insert(
147 "code".to_owned(),
148 Value::String(body.device_code.to_owned()),
149 );
150
151 Some(map)
152 }
153
154 #[allow(clippy::type_complexity)]
155 fn device_access_token_response_parsing(
156 &self,
157 response: &Response<Body>,
158 ) -> Option<
159 Result<
160 Result<
161 Result<
162 AccessTokenResponseSuccessfulBody<<Self as Provider>::Scope>,
163 AccessTokenResponseErrorBody,
164 >,
165 DeviceAccessTokenEndpointRetryReason,
166 >,
167 Box<dyn std::error::Error + Send + Sync + 'static>,
168 >,
169 > {
170 fn doing(
171 response: &Response<Body>,
172 ) -> Result<
173 Result<
174 AccessTokenResponseSuccessfulBody<<FacebookProviderForDevices as Provider>::Scope>,
175 FacebookDeviceAccessTokenResponseErrorBody,
176 >,
177 Box<dyn std::error::Error + Send + Sync + 'static>,
178 > {
179 if response.status().is_success() {
180 let map = serde_json::from_slice::<Map<String, Value>>(response.body())
181 .map_err(DeviceAccessTokenResponseParsingError::DeResponseBodyFailed)?;
182 if !map.contains_key("error") {
183 let body = serde_json::from_slice::<
184 AccessTokenResponseSuccessfulBody<
185 <FacebookProviderForDevices as Provider>::Scope,
186 >,
187 >(response.body())
188 .map_err(DeviceAccessTokenResponseParsingError::DeResponseBodyFailed)?;
189
190 return Ok(Ok(body));
191 }
192 }
193
194 let body = serde_json::from_slice::<FacebookDeviceAccessTokenResponseErrorBody>(
195 response.body(),
196 )
197 .map_err(DeviceAuthorizationResponseParsingError::DeResponseBodyFailed)?;
198 Ok(Err(body))
199 }
200
201 match doing(response) {
202 Ok(Ok(ok_body)) => Some(Ok(Ok(Ok(ok_body)))),
203 Ok(Err(err_body)) => match Result::<
204 DeviceAccessTokenEndpointRetryReason,
205 AccessTokenResponseErrorBody,
206 >::try_from(err_body)
207 {
208 Ok(Ok(reason)) => Some(Ok(Err(reason))),
209 Ok(Err(err_body)) => Some(Ok(Ok(Err(err_body)))),
210 Err(err) => Some(Err(err)),
211 },
212 Err(err) => Some(Err(err)),
213 }
214 }
215}
216
217#[derive(Serialize, Deserialize)]
219pub struct FacebookDeviceAuthorizationResponseSuccessfulBody {
220 pub code: DeviceCode,
221 pub user_code: UserCode,
222 pub verification_uri: VerificationUri,
223 #[serde(skip_serializing_if = "Option::is_none")]
224 pub verification_uri_complete: Option<VerificationUriComplete>,
225 pub expires_in: usize,
226 #[serde(skip_serializing_if = "Option::is_none")]
227 pub interval: Option<usize>,
228}
229impl From<FacebookDeviceAuthorizationResponseSuccessfulBody>
230 for DeviceAuthorizationResponseSuccessfulBody
231{
232 fn from(body: FacebookDeviceAuthorizationResponseSuccessfulBody) -> Self {
233 Self::new(
234 body.code.to_owned(),
235 body.user_code.to_owned(),
236 body.verification_uri.to_owned(),
237 body.verification_uri_complete.to_owned(),
238 body.expires_in.to_owned(),
239 body.interval.to_owned(),
240 )
241 }
242}
243
244#[derive(Serialize, Deserialize)]
245pub struct FacebookDeviceAuthorizationResponseErrorBody {
246 pub error: FacebookDeviceAuthorizationResponseErrorBodyError,
247}
248#[derive(Serialize, Deserialize)]
249pub struct FacebookDeviceAuthorizationResponseErrorBodyError {
250 pub message: String,
251}
252impl TryFrom<FacebookDeviceAuthorizationResponseErrorBody>
253 for DeviceAuthorizationResponseErrorBody
254{
255 type Error = Box<dyn std::error::Error + Send + Sync>;
256
257 fn try_from(body: FacebookDeviceAuthorizationResponseErrorBody) -> Result<Self, Self::Error> {
258 let mut body_new = Self::new(
259 AccessTokenResponseErrorBodyError::Other("".to_owned()),
260 Some(body.error.message.to_owned()),
261 None,
262 );
263 body_new.set_extra(
264 serde_json::to_value(body)
265 .map(|x| x.as_object().cloned())?
266 .ok_or_else(|| "unreachable".to_owned())?,
267 );
268
269 Ok(body_new)
270 }
271}
272
273#[derive(thiserror::Error, Debug)]
274pub enum DeviceAuthorizationResponseParsingError {
275 #[error("DeResponseBodyFailed {0}")]
277 DeResponseBodyFailed(SerdeJsonError),
278}
279
280#[derive(Serialize, Deserialize)]
282pub struct FacebookDeviceAccessTokenResponseErrorBody {
283 pub error: FacebookDeviceAccessTokenResponseErrorBodyError,
284}
285#[derive(Serialize, Deserialize)]
286pub struct FacebookDeviceAccessTokenResponseErrorBodyError {
287 pub message: String,
288 pub error_subcode: Option<isize>,
289}
290impl TryFrom<FacebookDeviceAccessTokenResponseErrorBody>
291 for Result<DeviceAccessTokenEndpointRetryReason, AccessTokenResponseErrorBody>
292{
293 type Error = Box<dyn std::error::Error + Send + Sync>;
294
295 fn try_from(body: FacebookDeviceAccessTokenResponseErrorBody) -> Result<Self, Self::Error> {
296 match body.error.error_subcode {
297 Some(1349174) => Ok(Ok(
298 DeviceAccessTokenEndpointRetryReason::AuthorizationPending,
299 )),
300 Some(1349172) => Ok(Ok(DeviceAccessTokenEndpointRetryReason::SlowDown)),
301 Some(1349152) => {
302 let mut body_new = AccessTokenResponseErrorBody::new(
303 AccessTokenResponseErrorBodyError::ExpiredToken,
304 Some(body.error.message.to_owned()),
305 None,
306 );
307 body_new.set_extra(
308 serde_json::to_value(body)
309 .map(|x| x.as_object().cloned())?
310 .ok_or_else(|| "unreachable".to_owned())?,
311 );
312
313 Ok(Err(body_new))
314 }
315 _ => {
316 let mut body_new = AccessTokenResponseErrorBody::new(
317 AccessTokenResponseErrorBodyError::Other("".to_owned()),
318 Some(body.error.message.to_owned()),
319 None,
320 );
321 body_new.set_extra(
322 serde_json::to_value(body)
323 .map(|x| x.as_object().cloned())?
324 .ok_or_else(|| "unreachable".to_owned())?,
325 );
326
327 Ok(Err(body_new))
328 }
329 }
330 }
331}
332
333#[derive(thiserror::Error, Debug)]
334pub enum DeviceAccessTokenResponseParsingError {
335 #[error("DeResponseBodyFailed {0}")]
337 DeResponseBodyFailed(SerdeJsonError),
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 use oauth2_client::{
345 device_authorization_grant::{DeviceAccessTokenEndpoint, DeviceAuthorizationEndpoint},
346 re_exports::{Endpoint as _, RetryableEndpoint as _},
347 };
348
349 #[test]
350 fn authorization_request() -> Result<(), Box<dyn std::error::Error>> {
351 let provider =
352 FacebookProviderForDevices::new("APP_ID".to_owned(), "CLIENT_TOKEN".to_owned())?;
353 let endpoint = DeviceAuthorizationEndpoint::new(
354 &provider,
355 vec![FacebookScope::Email, FacebookScope::PublicProfile],
356 );
357
358 let request = endpoint.render_request()?;
360
361 assert_eq!(
362 request.body(),
363 b"scope=email+public_profile&access_token=APP_ID%7CCLIENT_TOKEN"
364 );
365
366 Ok(())
367 }
368
369 #[test]
370 fn authorization_response() -> Result<(), Box<dyn std::error::Error>> {
371 let provider =
372 FacebookProviderForDevices::new("APP_ID".to_owned(), "CLIENT_TOKEN".to_owned())?;
373 let endpoint = DeviceAuthorizationEndpoint::new(
374 &provider,
375 vec![FacebookScope::Email, FacebookScope::PublicProfile],
376 );
377
378 let response_body =
380 include_str!("../tests/response_body_json_files/device_authorization.json");
381 let body_ret = endpoint
382 .parse_response(Response::builder().body(response_body.as_bytes().to_vec())?)?;
383 match body_ret {
384 Ok(body) => {
385 assert_eq!(body.device_code, "4c7c240847a4c10bf6850802c51dde1e")
386 }
387 Err(body) => panic!("{body:?}"),
388 }
389
390 let response_body = include_str!(
392 "../tests/response_body_json_files/device_authorization_err_when_no_access_token.json"
393 );
394 let body_ret = endpoint
395 .parse_response(Response::builder().body(response_body.as_bytes().to_vec())?)?;
396 match body_ret {
397 Ok(body) => panic!("{body:?}"),
398 Err(body) => assert_eq!(
399 body.error_description,
400 Some("(#190) This method must be called with a client access token".to_owned())
401 ),
402 }
403
404 Ok(())
405 }
406
407 #[test]
408 fn access_token_request() -> Result<(), Box<dyn std::error::Error>> {
409 let provider =
410 FacebookProviderForDevices::new("APP_ID".to_owned(), "CLIENT_TOKEN".to_owned())?;
411 let endpoint = DeviceAccessTokenEndpoint::new(
412 &provider,
413 DeviceAuthorizationResponseSuccessfulBody::new(
414 "DEVICE_CODE".to_owned(),
415 "".to_owned(),
416 "https://example.com".parse()?,
417 None,
418 0,
419 Some(5),
420 ),
421 );
422
423 let request = endpoint.render_request(None)?;
425
426 assert_eq!(request.body(), b"grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&device_code=DEVICE_CODE&access_token=APP_ID%7CCLIENT_TOKEN&code=DEVICE_CODE");
427
428 Ok(())
429 }
430
431 #[test]
432 fn access_token_response() -> Result<(), Box<dyn std::error::Error>> {
433 let provider =
434 FacebookProviderForDevices::new("APP_ID".to_owned(), "CLIENT_TOKEN".to_owned())?;
435 let endpoint = DeviceAccessTokenEndpoint::new(
436 &provider,
437 DeviceAuthorizationResponseSuccessfulBody::new(
438 "DEVICE_CODE".to_owned(),
439 "".to_owned(),
440 "https://example.com".parse()?,
441 None,
442 0,
443 Some(5),
444 ),
445 );
446
447 let response_body = include_str!(
449 "../tests/response_body_json_files/access_token_with_device_authorization_grant.json"
450 );
451 let body_ret = endpoint.parse_response(
452 Response::builder().body(response_body.as_bytes().to_vec())?,
453 None,
454 )?;
455 match body_ret {
456 Ok(Ok(body)) => {
457 let map = body.extra().unwrap();
458 assert_eq!(
459 map.get("data_access_expiration_time").unwrap().as_u64(),
460 Some(1644569029)
461 );
462 }
463 Ok(Err(body)) => panic!("{body:?}"),
464 Err(reason) => panic!("{reason:?}"),
465 }
466
467 let response_body = include_str!(
469 "../tests/response_body_json_files/device_access_token_err_when_1349174.json"
470 );
471 let body_ret = endpoint.parse_response(
472 Response::builder().body(response_body.as_bytes().to_vec())?,
473 None,
474 )?;
475 match body_ret {
476 Ok(Ok(body)) => panic!("{body:?}"),
477 Ok(Err(body)) => panic!("{body:?}"),
478 Err(reason) => assert_eq!(
479 reason,
480 DeviceAccessTokenEndpointRetryReason::AuthorizationPending
481 ),
482 }
483
484 Ok(())
485 }
486}