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}