azure_identity/
development.rs

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
//! Utilities for aiding in development
//!
//! These utilities should not be used in production
use crate::authorization_code_flow::AuthorizationCodeFlow;
use azure_core::{
    error::{Error, ErrorKind},
    Url,
};
use oauth2::{AuthorizationCode, CsrfToken};
use std::{
    io::{BufRead, BufReader, Write},
    net::TcpListener,
};
use tracing::debug;

/// A very naive implementation of a redirect server.
///
/// A ripoff of <https://github.com/ramosbugs/oauth2-rs/blob/master/examples/msgraph.rs>, stripped
/// down for simplicity. This server blocks until redirected to.
///
/// This implementation should only be used for testing.
pub fn naive_redirect_server(
    auth_obj: &AuthorizationCodeFlow,
    port: u16,
) -> azure_core::Result<AuthorizationCode> {
    let listener = TcpListener::bind(format!("127.0.0.1:{port}")).unwrap();

    // The server will terminate itself after collecting the first code.
    if let Some(mut stream) = listener.incoming().flatten().next() {
        let mut reader = BufReader::new(&stream);

        let mut request_line = String::new();
        reader.read_line(&mut request_line).unwrap();

        let Some(redirect_url) = request_line.split_whitespace().nth(1) else {
            return Err(Error::with_message(ErrorKind::Credential, || {
                format!("unexpected redirect url: {request_line}")
            }));
        };
        let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();

        debug!("url == {}", url);

        let code = match url.query_pairs().find(|(key, _)| key == "code") {
            Some((_, value)) => AuthorizationCode::new(value.into_owned()),
            None => {
                return Err(Error::message(
                    ErrorKind::Credential,
                    "query pair not found: code",
                ))
            }
        };

        let state = match url.query_pairs().find(|(key, _)| key == "state") {
            Some((_, value)) => CsrfToken::new(value.into_owned()),
            None => {
                return Err(Error::message(
                    ErrorKind::Credential,
                    "query pair not found: state",
                ))
            }
        };

        if state.secret() != auth_obj.csrf_state.secret() {
            return Err(Error::with_message(ErrorKind::Credential, || {
                format!(
                    "State secret mismatch: expected {}, received: {}",
                    auth_obj.csrf_state.secret(),
                    state.secret()
                )
            }));
        }

        let message = "Authentication complete. You can close this window now.";
        let response = format!(
            "HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
            message.len(),
            message
        );
        stream.write_all(response.as_bytes()).unwrap();

        return Ok(code);
    }

    unreachable!()
}