mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-12-29 21:00:54 +00:00
fix(mysql): auth plugins have an opaque command phase rather than a specific MORE_DATA phase
This commit is contained in:
parent
71f52961c9
commit
a5a3fb346d
@ -70,8 +70,9 @@ impl<Rt: Runtime> MySqlConnection<Rt> {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
AuthResponse::MoreData(data) => {
|
||||
AuthResponse::Command(command, data) => {
|
||||
if let Some(data) = handshake.auth_plugin.handle(
|
||||
command,
|
||||
data,
|
||||
&handshake.auth_plugin_data,
|
||||
options.get_password().unwrap_or_default(),
|
||||
@ -168,6 +169,8 @@ mod tests {
|
||||
|
||||
const SRV_PUBLIC_KEY: &[u8] = b"\x01-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwnXi3nr9TmN+NF49A3Y7\nUBnAVhApNJy2cmuf/y6vFM9eHFu5T80Ij1qYc6c79oAGA8nNNCFQL+0j5De88cln\nKrlzq/Ab3U+j5SqgNwk//F6Y3iyjV4L7feSDqjpcheFzkjEslbm/yoRwQ78AAU6s\nqA0hcFuh66mcvnotDrvZAGQ8U2EbbZa6oiR3wrgbzifSKq767g65zIrCpoyxzKMH\nAETSDIaMKpFio4dRATKT5ASQtPoIyxSBmjRtc22sqlhEeiejEMsJzd6Bliuait+A\nkTXL6G1Tbam26Dok/L88CnTAWAkLwTA3bjPcS8Zl9gTsJvoiMuwW1UPEVV/aJ11Z\n/wIDAQAB\n-----END PUBLIC KEY-----\n";
|
||||
const SRV_AUTH_OK: &[u8] = b"\0\0\0\x02\0\0\0";
|
||||
const SRV_AUTH_ERR: &[u8] =
|
||||
b"\xff\x15\x04#28000Access denied for user 'root'@'172.17.0.1' (using password: YES)";
|
||||
const SRV_AUTH_MORE_CONTINUE: &[u8] = b"\x01\x04";
|
||||
const SRV_AUTH_MORE_OK: &[u8] = b"\x01\x03";
|
||||
const SRV_SWITCH_CACHING_SHA2_AUTH: &[u8] =
|
||||
@ -397,6 +400,30 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail_connect_err() -> anyhow::Result<()> {
|
||||
block_on(async {
|
||||
let mut mock = Mock::stream();
|
||||
|
||||
mock.write_packet_async(0, SRV_HANDSHAKE_DEFAULT_NATIVE_AUTH).await?;
|
||||
mock.write_packet_async(2, SRV_AUTH_ERR).await?;
|
||||
|
||||
let err = MySqlConnectOptions::new()
|
||||
.port(mock.port())
|
||||
.username("root")
|
||||
.connect::<MySqlConnection<Mock>, _>()
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"1045 (28000): Access denied for user \'root\'@\'172.17.0.1\' (using password: YES)"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_connect_old_auth() -> anyhow::Result<()> {
|
||||
block_on(async {
|
||||
|
||||
@ -29,6 +29,7 @@ pub(crate) trait AuthPlugin: 'static + Debug + Send + Sync {
|
||||
// if the plugin returns Some(_) that is sent back to MySQL
|
||||
fn handle(
|
||||
&self,
|
||||
command: u8,
|
||||
data: Bytes,
|
||||
nonce: &Chain<Bytes, Bytes>,
|
||||
password: &str,
|
||||
|
||||
@ -51,6 +51,7 @@ impl super::AuthPlugin for CachingSha2AuthPlugin {
|
||||
|
||||
fn handle(
|
||||
&self,
|
||||
command: u8,
|
||||
data: Bytes,
|
||||
nonce: &Chain<Bytes, Bytes>,
|
||||
password: &str,
|
||||
@ -58,6 +59,13 @@ impl super::AuthPlugin for CachingSha2AuthPlugin {
|
||||
const AUTH_SUCCESS: u8 = 0x3;
|
||||
const AUTH_CONTINUE: u8 = 0x4;
|
||||
|
||||
if command != 0x01 {
|
||||
return Err(super::err_msg(
|
||||
self.name(),
|
||||
&format!("Received 0x{:x} but expected 0x1 (MORE DATA)", command),
|
||||
));
|
||||
}
|
||||
|
||||
match data[0] {
|
||||
// good to go, return nothing
|
||||
AUTH_SUCCESS => Ok(None),
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use bytes::buf::Chain;
|
||||
use bytes::Bytes;
|
||||
use sqlx_core::{Error, Result};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Dialog authentication implementation
|
||||
///
|
||||
@ -21,6 +22,7 @@ impl super::AuthPlugin for DialogAuthPlugin {
|
||||
|
||||
fn handle(
|
||||
&self,
|
||||
_command: u8,
|
||||
_data: Bytes,
|
||||
_nonce: &Chain<Bytes, Bytes>,
|
||||
_password: &str,
|
||||
|
||||
@ -49,6 +49,7 @@ impl super::AuthPlugin for NativeAuthPlugin {
|
||||
|
||||
fn handle(
|
||||
&self,
|
||||
_command: u8,
|
||||
_data: Bytes,
|
||||
_nonce: &Chain<Bytes, Bytes>,
|
||||
_password: &str,
|
||||
|
||||
@ -30,10 +30,18 @@ impl super::AuthPlugin for Sha256AuthPlugin {
|
||||
|
||||
fn handle(
|
||||
&self,
|
||||
command: u8,
|
||||
data: Bytes,
|
||||
nonce: &Chain<Bytes, Bytes>,
|
||||
password: &str,
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
if command != 0x01 {
|
||||
return Err(super::err_msg(
|
||||
self.name(),
|
||||
&format!("Received 0x{:x} but expected 0x1 (MORE DATA)", command),
|
||||
));
|
||||
}
|
||||
|
||||
let rsa_pub_key = data;
|
||||
let encrypted = super::rsa::encrypt(self.name(), &rsa_pub_key, password, nonce)?;
|
||||
|
||||
|
||||
@ -10,25 +10,28 @@ use crate::MySqlDatabaseError;
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum AuthResponse {
|
||||
End(ResultPacket),
|
||||
MoreData(Bytes),
|
||||
Command(u8, Bytes),
|
||||
Switch(AuthSwitch),
|
||||
}
|
||||
|
||||
impl Deserialize<'_, Capabilities> for AuthResponse {
|
||||
fn deserialize_with(buf: Bytes, capabilities: Capabilities) -> Result<Self> {
|
||||
match buf.get(0) {
|
||||
Some(0x00) => ResultPacket::deserialize_with(buf, capabilities).map(Self::End),
|
||||
Some(0x01) => Ok(Self::MoreData(buf.slice(1..))),
|
||||
// OK or ERR -> end the auth cycle
|
||||
Some(0x00) | Some(0xff) => {
|
||||
ResultPacket::deserialize_with(buf, capabilities).map(Self::End)
|
||||
}
|
||||
|
||||
// switch to another auth plugin
|
||||
Some(0xfe) => AuthSwitch::deserialize(buf).map(Self::Switch),
|
||||
|
||||
Some(tag) => Err(MySqlDatabaseError::malformed_packet(&format!(
|
||||
"Received 0x{:x} but expected one of: 0x0 (OK), 0x1 (MORE DATA), or 0xfe (SWITCH) for auth response",
|
||||
tag
|
||||
)).into()),
|
||||
// send a command to the active auth plugin
|
||||
Some(command) => Ok(Self::Command(*command, buf.slice(1..))),
|
||||
|
||||
None => Err(MySqlDatabaseError::malformed_packet(
|
||||
"Received no bytes for auth response",
|
||||
).into()),
|
||||
None => {
|
||||
Err(MySqlDatabaseError::malformed_packet("Received no bytes for auth response")
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user