Repository: launchbadge/sqlx
File: sqlx-mysql/src/protocol/connect/auth_switch.rs (Rust)

Code before:
// https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_connection_phase_packets_protocol_auth_switch_request.html

#[derive(Debug)]
pub struct AuthSwitchRequest {
    pub plugin: AuthPlugin,
    pub data: Bytes,
}

impl Decode<'_> for AuthSwitchRequest {
    fn decode_with(mut buf: Bytes, _: ()) -> Result<Self, Error> {
        let header = buf.get_u8();
        if header != 0xfe {
            return Err(err_protocol!(
                "expected 0xfe (AUTH_SWITCH) but found 0x{:x}",
                header
            ));
        }

        let plugin = buf.get_str_nul()?.parse()?;

        // See: https://github.com/mysql/mysql-server/blob/ea7d2e2d16ac03afdd9cb72a972a95981107bf51/sql/auth/sha2_password.cc#L942
        let data = if buf.len() != 21 {
            if matches!(plugin, AuthPlugin::MySqlClearPassword) {
                // Contrary to the MySQL protocol, AWS Aurora with IAM sends
                // no data. That is fine because the MySQL protocol says to
                // ignore any data sent.
                Bytes::new()
            } else {
                return Err(err_protocol!(
                    "expected 21 bytes but found {} bytes",
                    buf.len()
                ));
            }
        } else {
            let data = buf.get_bytes(20);
            buf.advance(1); // NUL-terminator
            data
        };

        Ok(Self { plugin, data })
    }
}

#[derive(Debug)]
pub struct AuthSwitchResponse(pub Vec<u8>);

impl Encode<'_, Capabilities> for AuthSwitchResponse {
    fn encode_with(&self, buf: &mut Vec<u8>, _: Capabilities) {
        buf.extend_from_slice(&self.0);
    }
}

Review comment (security):
Can we link documentation that corroborates these comments? Like https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_authentication_methods_clear_text_password.html
or
https://dev.mysql.com/doc/refman/8.0/en/cleartext-pluggable-authentication.html

If the `MySqlClearPassword` protocol wants you to ignore data - and we're making a specific exception for the aurora no-data scenario, perhaps we can make that more clear like this.

```
if matches!(plugin, AuthPlugin::MySqlClearPassword) && buf.is_empty() {
  // Contrary to the MySQL protocol, AWS Aurora with IAM sends no data.
  // In this scenario, it is ok to proceed with an empty data buffer.
  return Ok(Self { plugin, data: Bytes::new() })
}
```

And then have the rest of the code left the way it is.

Code after:
// https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_connection_phase_packets_protocol_auth_switch_request.html

#[derive(Debug)]
pub struct AuthSwitchRequest {
    pub plugin: AuthPlugin,
    pub data: Bytes,
}

impl Decode<'_, bool> for AuthSwitchRequest {
    fn decode_with(mut buf: Bytes, enable_cleartext_plugin: bool) -> Result<Self, Error> {
        let header = buf.get_u8();
        if header != 0xfe {
            return Err(err_protocol!(
                "expected 0xfe (AUTH_SWITCH) but found 0x{:x}",
                header
            ));
        }

        let plugin = buf.get_str_nul()?.parse()?;

        if matches!(plugin, AuthPlugin::MySqlClearPassword) && !enable_cleartext_plugin {
            return Err(err_protocol!("mysql_cleartext_plugin disabled"));
        }

        if matches!(plugin, AuthPlugin::MySqlClearPassword) && buf.is_empty() {
            // Contrary to the MySQL protocol, AWS Aurora with IAM sends
            // no data. That is fine because the mysql_clear_password says to
            // ignore any data sent.
            // See: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_authentication_methods_clear_text_password.html
            return Ok(Self {
                plugin,
                data: Bytes::new(),
            });
        }

        // See: https://github.com/mysql/mysql-server/blob/ea7d2e2d16ac03afdd9cb72a972a95981107bf51/sql/auth/sha2_password.cc#L942
        if buf.len() != 21 {
            return Err(err_protocol!(
                "expected 21 bytes but found {} bytes",
                buf.len()
            ));
        }
        let data = buf.get_bytes(20);
        buf.advance(1); // NUL-terminator

        Ok(Self { plugin, data })
    }
}

#[derive(Debug)]
pub struct AuthSwitchResponse(pub Vec<u8>);