in finagle-mysql/src/main/scala/com/twitter/finagle/mysql/AuthNegotiation.scala [176:270]
def doAuth(initMessage: HandshakeResponse, initAuthInfo: AuthInfo): Future[Result] = step(
State.Init(initMessage, initAuthInfo))
/**
* Dispatch a message, then read and return the result.
*/
private def dispatch(msg: ProtocolMessage): Future[Result] =
transport
.write(msg.toPacket)
.before(transport.read())
.flatMap(resultDecoder)
/**
* The state machine that determines which sends the correct message
* depending on the state (Init, Switch, or MoreData) it is passed.
*/
private def step(state: State): Future[Result] = state match {
// dispatch(Init Message) -> AuthSwitchRequest | <terminate>
case State.Init(msg, info) =>
dispatch(msg).flatMap {
// Change state to Switch.
case res: AuthSwitchRequest => step(State.Switch(res, info))
// Or terminate the state machine with OK, Error, or an Exception.
case ok: OK => Future.value(ok)
case error: Error => Future.value(error)
case m =>
Future.exception(
new NegotiationFailure(s"Unrecognized or unexpected message from server: $m"))
}
// dispatch(AuthSwitchResponse) -> AuthMoreData | <terminate>
case State.Switch(msg, info) =>
val req = makeAuthSwitchResponse((msg.seqNum + 1).toShort, msg.pluginData, info)
dispatch(req).flatMap {
// Change state to MoreData.
case res: AuthMoreDataFromServer =>
val nextInfo = info.copy(salt = Some(msg.pluginData))
step(State.MoreData(res, nextInfo))
// Or terminate the state machine with OK, Error, or an Exception.
case ok: OK => Future.value(ok)
case error: Error => Future.value(error)
case m =>
Future.exception(
new NegotiationFailure(s"Unrecognized or unexpected message from server: $m"))
}
// AuthMoreData -> AuthMoreData | <terminate>
case State.MoreData(msg, info) =>
val nextState: Result => Future[Result] = {
// Stay in the MoreData state.
case more: AuthMoreDataFromServer => step(State.MoreData(more, info))
// Or terminate the state machine with OK, Error, or an Exception.
case ok: OK => Future.value(ok)
case error: Error => Future.value(error)
case m =>
Future.exception(
new NegotiationFailure(s"Unrecognized or unexpected message from server: $m"))
}
// The server sends three AuthMoreDataTypes, and the PerformFullAuth
// type is handled differently depending on if TLS is enabled or not.
// If TLS is not enabled, then we perform full auth with the server's
// RSA public key.
msg.moreDataType match {
// The user is already cached in the server so we get a fast auth success.
case FastAuthSuccess =>
info.fastAuthSuccessCounter.incr()
transport.read().flatMap(resultDecoder) // Server sends separate OK packet
// We previously sent the server the request for the RSA public key
// This AuthMoreData packet contains the server's public key.
case NeedPublicKey =>
dispatch(makeAuthMoreDataWithServersSentRsaKey(msg, info)).flatMap(nextState)
// When TLS is enabled, we send the password as plaintext.
case PerformFullAuth if info.tlsEnabled =>
dispatch(makeAuthMoreDataWithPlaintextPassword(msg, info)).flatMap(nextState)
// When TLS is not enabled we either request the RSA public key from the
// server or send the AuthMoreData packet with the password encrypted with
// the locally stored RSA public key of the server. We determine if we need
// to send the request for the RSA public key to the server by checking if a
// path to the locally stored key is provided through the
// PathToServerRsaPublicKey param.
case PerformFullAuth if !info.tlsEnabled =>
if (info.settings.pathToServerRsaPublicKey.nonEmpty) {
val rsaKey = PasswordUtils.readFromPath(info.settings.pathToServerRsaPublicKey)
val req = makeAuthMoreDataWithRsaKeyEncryptedPassword(msg, info, rsaKey)
dispatch(req).flatMap(nextState)
} else {
// Public key unknown to client, request the public key from the server.
dispatch(makePublicKeyRequestToServer(msg)).flatMap(nextState)
}
}
}