azure_identity/
development.rs

1//! Utilities for aiding in development
2//!
3//! These utilities should not be used in production
4use crate::authorization_code_flow::AuthorizationCodeFlow;
5use azure_core::{
6    error::{Error, ErrorKind},
7    Url,
8};
9use oauth2::{AuthorizationCode, CsrfToken};
10use std::{
11    io::{BufRead, BufReader, Write},
12    net::TcpListener,
13};
14use tracing::debug;
15
16/// A very naive implementation of a redirect server.
17///
18/// A ripoff of <https://github.com/ramosbugs/oauth2-rs/blob/master/examples/msgraph.rs>, stripped
19/// down for simplicity. This server blocks until redirected to.
20///
21/// This implementation should only be used for testing.
22pub fn naive_redirect_server(
23    auth_obj: &AuthorizationCodeFlow,
24    port: u16,
25) -> azure_core::Result<AuthorizationCode> {
26    let listener = TcpListener::bind(format!("127.0.0.1:{port}")).unwrap();
27
28    // The server will terminate itself after collecting the first code.
29    if let Some(mut stream) = listener.incoming().flatten().next() {
30        let mut reader = BufReader::new(&stream);
31
32        let mut request_line = String::new();
33        reader.read_line(&mut request_line).unwrap();
34
35        let Some(redirect_url) = request_line.split_whitespace().nth(1) else {
36            return Err(Error::with_message(ErrorKind::Credential, || {
37                format!("unexpected redirect url: {request_line}")
38            }));
39        };
40        let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
41
42        debug!("url == {}", url);
43
44        let code = match url.query_pairs().find(|(key, _)| key == "code") {
45            Some((_, value)) => AuthorizationCode::new(value.into_owned()),
46            None => {
47                return Err(Error::message(
48                    ErrorKind::Credential,
49                    "query pair not found: code",
50                ))
51            }
52        };
53
54        let state = match url.query_pairs().find(|(key, _)| key == "state") {
55            Some((_, value)) => CsrfToken::new(value.into_owned()),
56            None => {
57                return Err(Error::message(
58                    ErrorKind::Credential,
59                    "query pair not found: state",
60                ))
61            }
62        };
63
64        if state.secret() != auth_obj.csrf_state.secret() {
65            return Err(Error::with_message(ErrorKind::Credential, || {
66                format!(
67                    "State secret mismatch: expected {}, received: {}",
68                    auth_obj.csrf_state.secret(),
69                    state.secret()
70                )
71            }));
72        }
73
74        let message = "Authentication complete. You can close this window now.";
75        let response = format!(
76            "HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
77            message.len(),
78            message
79        );
80        stream.write_all(response.as_bytes()).unwrap();
81
82        return Ok(code);
83    }
84
85    unreachable!()
86}