archan937/monetdb

View on GitHub
lib/monetdb/connection/setup.rb

Summary

Maintainability
A
45 mins
Test Coverage
module MonetDB
  class Connection
    module Setup
    private

      def setup
        authenticate
        set_timezone_interval
        set_reply_size
      end

      def authenticate
        obtain_server_challenge!

        write authentication_string
        response = read

        case msg_chr(response)
        when MSG_ERROR
          raise MonetDB::AuthenticationError, "Authentication failed: #{response}"
        when MSG_REDIRECT
          authentication_redirect response
        else
          @authentication_redirects = nil
          true
        end
      end

      def obtain_server_challenge!
        config.merge! server_challenge
        assert_supported_protocol!
        select_supported_auth_type!
      end

      def server_challenge
        keys_and_values = [:salt, :server_name, :protocol, :auth_types, :server_endianness, :password_digest_method].zip read.split(":")
        Hash[keys_and_values]
      end

      def assert_supported_protocol!
        unless PROTOCOLS.include?(config[:protocol])
          raise MonetDB::ProtocolError, "Protocol '#{config[:protocol]}' not supported. Only #{PROTOCOLS.collect{|x| "'#{x}'"}.join(", ")}."
        end
      end

      def select_supported_auth_type!
        unless config[:auth_type] = (AUTH_TYPES & (auth_types = config[:auth_types].split(","))).first
          raise MonetDB::AuthenticationError, "Authentication types (#{auth_types.join(", ")}) not supported. Only #{AUTH_TYPES.join(", ")}."
        end
      end

      def authentication_string
        [ENDIANNESS, config[:username], "{#{config[:auth_type]}}#{authentication_hashsum}", LANG, config[:database], ""].join(":")
      end

      def authentication_hashsum
        auth_type, password, password_digest_method = config.values_at(:auth_type, :password, :password_digest_method)

        case auth_type
        when AUTH_MD5, AUTH_SHA512, AUTH_SHA384, AUTH_SHA256, AUTH_SHA1
          password = hexdigest(password_digest_method, password) if config[:protocol] == MAPI_V9
          hexdigest(auth_type, password + config[:salt])
        when AUTH_PLAIN
          config[:password] + config[:salt]
        end
      end

      def hexdigest(method, value)
        Digest.const_get(method).new.hexdigest(value)
      end

      def authentication_redirect(response)
        unless response.split("\n").detect{|x| x.match(/^\^mapi:(.*)/)}
          raise MonetDB::AuthenticationError, "Authentication redirect not supported: #{response}"
        end

        begin
          scheme, userinfo, host, port, registry, database = URI.split(uri = $1)
        rescue URI::InvalidURIError
          raise MonetDB::AuthenticationError, "Invalid authentication redirect URI: #{uri}"
        end

        case scheme
        when "merovingian"
          if (@authentication_redirects ||= 0) < 5
            @authentication_redirects += 1
            authenticate
          else
            raise MonetDB::AuthenticationError, "Merovingian: Too many redirects while proxying"
          end
        when "monetdb"
          config[:host] = host
          config[:port] = port
          connect
        else
          raise MonetDB::AuthenticationError, "Cannot authenticate"
        end
      end

      def set_timezone_interval
        return false if @timezone_interval_set

        offset = Time.now.gmt_offset / 3600

        # BT: patch to allow for negative offsets from GMT

        # interval = "'+#{offset.to_s.rjust(2, "0")}:00'"

        offset_sign = offset<0?"-":"+"
        offset = offset.abs
        interval = "'#{offset_sign.to_s}#{offset.to_s.rjust(2, "0")}:00'"

        # BT: end patch

        write "sSET TIME ZONE INTERVAL #{interval} HOUR TO MINUTE;"
        response = read

        raise CommandError, "Unable to set timezone interval: #{response}" if msg?(response, MSG_ERROR)
        @timezone_interval_set = true
      end

      def set_reply_size
        return false if @reply_size_set

        write "Xreply_size #{REPLY_SIZE}\n"
        response = read

        raise CommandError, "Unable to set reply size: #{response}" if msg?(response, MSG_ERROR)
        @reply_size_set = true
      end

    end
  end
end