def doAuth()

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)
          }
      }
  }