require_relative '../common'
require 'net/ssh/authentication/session'

module Authentication

  class TestSession < NetSSHTest
    include Net::SSH::Transport::Constants
    include Net::SSH::Authentication::Constants

    def test_constructor_should_set_defaults
      assert_equal %w[none publickey password keyboard-interactive], session.auth_methods
      assert_equal session.auth_methods, session.allowed_auth_methods
    end

    def test_authenticate_should_continue_if_method_disallowed
      transport.expect do |t, packet|
        assert_equal SERVICE_REQUEST, packet.type
        assert_equal "ssh-userauth", packet.read_string
        t.return(SERVICE_ACCEPT)
      end

      Net::SSH::Authentication::Methods::Publickey.any_instance.expects(:authenticate).with("next service", "username", "password").raises(Net::SSH::Authentication::DisallowedMethod)
      Net::SSH::Authentication::Methods::Password.any_instance.expects(:authenticate).with("next service", "username", "password").returns(true)
      Net::SSH::Authentication::Methods::None.any_instance.expects(:authenticate).with("next service", "username", "password").returns(false)

      assert session.authenticate("next service", "username", "password")
    end

    def test_authenticate_should_raise_error_if_service_request_fails
      transport.expect do |t, packet|
        assert_equal SERVICE_REQUEST, packet.type
        assert_equal "ssh-userauth", packet.read_string
        t.return(255)
      end

      assert_raises(Net::SSH::Exception) { session.authenticate("next service", "username", "password") }
    end

    def test_authenticate_should_return_false_if_all_auth_methods_fail
      transport.expect do |t, packet|
        assert_equal SERVICE_REQUEST, packet.type
        assert_equal "ssh-userauth", packet.read_string
        t.return(SERVICE_ACCEPT)
      end

      Net::SSH::Authentication::Methods::Publickey.any_instance.expects(:authenticate).with("next service", "username", "password").returns(false)
      Net::SSH::Authentication::Methods::Password.any_instance.expects(:authenticate).with("next service", "username", "password").returns(false)
      Net::SSH::Authentication::Methods::KeyboardInteractive.any_instance.expects(:authenticate).with("next service", "username", "password").returns(false)
      Net::SSH::Authentication::Methods::None.any_instance.expects(:authenticate).with("next service", "username", "password").returns(false)

      assert_equal false, session.authenticate("next service", "username", "password")
    end

    def test_next_message_should_silently_handle_USERAUTH_BANNER_packets
      transport.return(USERAUTH_BANNER, :string, "Howdy, folks!")
      transport.return(SERVICE_ACCEPT)
      assert_equal SERVICE_ACCEPT, session.next_message.type
    end

    def test_next_message_should_understand_USERAUTH_FAILURE
      transport.return(USERAUTH_FAILURE, :string, "a,b,c", :bool, false)
      packet = session.next_message
      assert_equal USERAUTH_FAILURE, packet.type
      assert_equal %w[a b c], session.allowed_auth_methods
    end

    (60..79).each do |type|
      define_method("test_next_message_should_return_packets_of_type_#{type}") do
        transport.return(type)
        assert_equal type, session.next_message.type
      end
    end

    def test_next_message_should_understand_USERAUTH_SUCCESS
      transport.return(USERAUTH_SUCCESS)
      assert !transport.hints[:authenticated]
      assert_equal USERAUTH_SUCCESS, session.next_message.type
      assert transport.hints[:authenticated]
    end

    def test_next_message_should_raise_error_on_unrecognized_packet_types
      transport.return(1)
      assert_raises(Net::SSH::Exception) { session.next_message }
    end

    def test_expect_message_should_raise_exception_if_next_packet_is_not_expected_type
      transport.return(SERVICE_ACCEPT)
      assert_raises(Net::SSH::Exception) { session.expect_message(USERAUTH_BANNER) }
    end

    def test_expect_message_should_return_packet_if_next_packet_is_expected_type
      transport.return(SERVICE_ACCEPT)
      assert_equal SERVICE_ACCEPT, session.expect_message(SERVICE_ACCEPT).type
    end

    def test_uses_some_default_keys_if_none_are_provided
      File.stubs(:file?).returns(false)

      file_on_filesystem("~/.ssh/id_rsa", default_private_key)

      transport.expect do |t, packet|
        assert_equal SERVICE_REQUEST, packet.type
        assert_equal "ssh-userauth", packet.read_string
        t.return(SERVICE_ACCEPT)
      end

      transport.expect do |t, packet|
        assert_none_request packet
        t.return(USERAUTH_FAILURE, :string, "publickey")
      end

      transport.expect do |t, packet|
        assert_public_key_request default_public_key, packet
        t.return(USERAUTH_FAILURE, :string, "publickey")
      end

      session.authenticate("next service", "username")
    end

    def test_does_not_use_default_keys_if_keys_are_present_in_options
      File.stubs(:file?).returns(false)

      file_on_filesystem("~/.ssh/id_rsa", default_private_key)
      file_on_filesystem("custom_rsa_id", custom_private_key)

      transport.expect do |t, packet|
        assert_equal SERVICE_REQUEST, packet.type
        assert_equal "ssh-userauth", packet.read_string
        t.return(SERVICE_ACCEPT)
      end

      transport.expect do |t, packet|
        assert_none_request packet
        t.return(USERAUTH_FAILURE, :string, "publickey")
      end

      transport.expect do |t, packet|
        assert_public_key_request custom_public_key, packet
        t.return(USERAUTH_FAILURE, :string, "publickey")
      end

      session(keys: "custom_rsa_id").authenticate("next service", "username")
    end

    def test_does_not_use_default_keys_if_key_data_are_present_in_options
      File.stubs(:file?).returns(false)

      file_on_filesystem("~/.ssh/id_rsa", default_private_key)

      transport.expect do |t, packet|
        assert_equal SERVICE_REQUEST, packet.type
        assert_equal "ssh-userauth", packet.read_string
        t.return(SERVICE_ACCEPT)
      end

      transport.expect do |t, packet|
        assert_none_request packet
        t.return(USERAUTH_FAILURE, :string, "publickey")
      end

      transport.expect do |t, packet|
        assert_public_key_request custom_public_key, packet
        t.return(USERAUTH_FAILURE, :string, "publickey")
      end

      session(key_data: custom_private_key).authenticate("next service", "username")
    end

    private

    def session(options = {})
      session_opts = options.clone
      session_opts[:pubkey_algorithms] = %w[ssh-rsa]
      @session ||= Net::SSH::Authentication::Session.new(transport(options), session_opts)
    end

    def transport(options={})
      @transport ||= MockTransport.new(options)
    end

    def assert_none_request(packet)
      assert_equal "username", packet.read_string
      assert_equal "next service", packet.read_string
      assert_equal "none", packet.read_string
    end

    def assert_public_key_request(public_key, packet)
      assert_equal "username", packet.read_string
      assert_equal "next service", packet.read_string
      assert_equal "publickey", packet.read_string
      assert_equal false, packet.read_bool
      assert_equal "ssh-rsa", packet.read_string
      key_in_packet = Net::SSH::Buffer.new(packet.read_string).read_key
      assert_equal public_key, key_in_packet.to_pem
    end

    def file_on_filesystem(name, contents)
      path = File.expand_path(name)
      File.stubs(:read).with(path).returns(contents)
      File.stubs(:file?).with(path).returns(true)
      File.stubs(:readable?).with(path).returns(true)
    end

    def custom_private_key
      <<~EOF
        -----BEGIN PRIVATE KEY-----
        MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3id5gZ6bglJth
        yli8JNaRxhsqKwwPlReEI/mplzz5IP6gWQ92LogXbdBXtHf9ZpA53BeLmtcNBEY0
        Ygd7sPBhlHABS5D5///zltSSX2+L5GCEiC6dpfGsySjqymWF+SZ2PaqfZbkWLmCD
        9u4ysueaHf7xbF6txGprNp69efttWxdy+vU5tno7HVxemMZQUalpShFrdAYKKXEo
        cV7MtbkQjzubS14gaWGpWCXIl9uNKQeHpLKtre1Qn5Ft/zVpCHmhLQcYDuB1LAj9
        7eoev4rIiOE2sfdkvKDlmFxvzq3myYH4o27WwAg9OZ5SBusn2zesKkRCBBEZ55rl
        uVknOGHXAgMBAAECggEAZE0U2OxsNxkfXS6+lXswQ5PW7pF90towcsdSPgrniGIu
        pKRnHbfKKbuaewOl+zZcpTIRL/rbgUKPtzrHSiJlC36aQyrvvJ/ZWV5ZJvC+vd19
        nY/qob65NyrrkHwxRSjmiwGiR9/IaUXI+vUsMUqx5Ph1hawqhZ3sZlEAKR4LeDO8
        M+OguG77jLaqj5/SNfi+GwyUDe85de4VfEG4S9HrMQk2Cp66rx0BqDnCLacyFQaI
        R0VczMXTU52q0uETmgUr8G9A1SaRc5ZWKAfZwxJTvqdIImWC9E+CY7wm+mZD4FE6
        iVzVC0ngcdEd596kTDdU2BPVMluWzLkfqIrTt/5CeQKBgQDzgRzCPNxFtai6RAIi
        ekBSHqrDnrbeTaw32GVq5ACk1Zfk2I0svctz1iQ9qJ2SRINpygQhcyJKQ4r/LXi1
        7Av9H/d6QV4T2AZzS4WcqBkxxRXFUfARtnKChzuCzNt9tNz4EZiv75RyQmztGZjV
        i94+ZvCyqup5be4Svf4MBxin9QKBgQDA9P4nHzFWZakTMei78LGb/4Auc+r0rZp7
        8xg8Z92tvrDeJjMdesdhiFrPP1qiSYHnQ81MSWpn6BycBsHZqitejQmYnYput/s4
        qG+m7SrkN8WL6rijYsbB+U14VDjMlBlOgcEgjlSNU2oeS+68u+uVI/fgyXcXn4Jq
        33TSWSgfGwKBgA2tRdE/G9wqfOShZ0FKfoxePpcoNfs8f5zPYbrkPYkEmjh3VU6b
        Bm9mKrjv3JHXmU3608qRLe7f5lG42xvUu0OnZP4P59nTe2FEb6fB5VBfUn63wHUu
        OzZLpDMPkJB59SNV0a6oFT1pr7aNhoEQDxaQL5rJcMwLOaEB3OAOEft1AoGASz7+
        4Zi7b7rDPVYIMUpCqNfxT6wqovIUPWPmPqAuhXPIm0kAQ+2+VN2MtCc7m+/Ydawu
        IiK7GPweNAY6kDxZH00WweolstmSYVzl9Y2lXUwWgGKvUB/T7I7g1Bzb7YOPftsA
        ykZW2Kn/xwLLfdQ2oXleT82g4Jh2jmDHuMPF7qMCgYEA6QF45PvOgnrJessgmwO/
        dEmkLl07PQYJPGZLaZteuWrvfMrn+AiW5aAdHzhzNaOtNy5B3T7zGUHtgxXegqgd
        /QdCVCJgnZUO/zdAxkr22dDn+WEXkL4wgBVStQvvnQp9C2NJcoOExvex5PLzKWQg
        WEKt5v3QsUEgVrzkM4K9UbI=
        -----END PRIVATE KEY-----
      EOF
    end

    def custom_public_key
      <<~EOF
        -----BEGIN PUBLIC KEY-----
        MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt4neYGem4JSbYcpYvCTW
        kcYbKisMD5UXhCP5qZc8+SD+oFkPdi6IF23QV7R3/WaQOdwXi5rXDQRGNGIHe7Dw
        YZRwAUuQ+f//85bUkl9vi+RghIgunaXxrMko6splhfkmdj2qn2W5Fi5gg/buMrLn
        mh3+8WxercRqazaevXn7bVsXcvr1ObZ6Ox1cXpjGUFGpaUoRa3QGCilxKHFezLW5
        EI87m0teIGlhqVglyJfbjSkHh6Syra3tUJ+Rbf81aQh5oS0HGA7gdSwI/e3qHr+K
        yIjhNrH3ZLyg5Zhcb86t5smB+KNu1sAIPTmeUgbrJ9s3rCpEQgQRGeea5blZJzhh
        1wIDAQAB
        -----END PUBLIC KEY-----
      EOF
    end

    def default_private_key
      <<~EOF
        -----BEGIN RSA PRIVATE KEY-----
        MIIEpAIBAAKCAQEAxbz0rp+Z7MklMtSkfiRfceOeTOhOgkGqonCL1B0MRzSjA3yf
        onvEobQNYv7uyQ+ZMGT9RL7AlUSUxeWF00A/O6kuwfs4JlPS/FMPy/B2V0UmoteT
        p40LmclZHpKZs9yKmgkfa5j8Jjvd/VvV1r/DbkHjZetIe07pSnP3EOAG7sjyV7yr
        HPvkgG5h/Vn2U19vTsvYIENcj5OCLF7eUSJZ/6m4qem+wZ4/9cau5E2t57oS8bTd
        5k00Jn0E+qRVionLVLtHXKnr0nWlGPinL+UhKBMhLA6Olm5Y8W77sYcUSvlJMy4G
        mpIvnWFKQE5vim4zKt3dBF256QPmRCWPTQ+sxwIDAQABAoIBAQCG+vrILVjEo3ZK
        IY/8L9Ybh3arJzVYg3z4j/1TmVSlUtAodC0AnJ5Yh/FPb5kPFR/MQlQFVnVeL8ei
        45Ab6dKAZnftoRDuUPBIoGa7H3WZEzJRnPlFOen+W80DKq3TcqwGhE23hGIzs1BR
        QBxUEOlWXZHeI+OBkRd9ZHX2RgdVfhGK1eCRGkxVUx6lygK7RcLSDJgPGvTUL+Gz
        xF4D03pDo2r6oghNk3Fbw4GMXMBLfrKfiee/QyBLEkq+nykVioxXO16ShJfxOzM4
        Pt6/7XJW7uMBGSblS89svrsn7i+29wcgkX4rGWyswV6xicpLNBEmkYx119QKLGBk
        a1QebsYxAoGBAOJL/KEGyO8z9l+b2xzWlAURCNJEqPgeAk6ck7woA+nnj2KH+/51
        1vvbQlvdwN1eP753g3eACvro7XQmVpJzqwBqAGyxtgPoI4F+HR++3lIOA+zfZs3p
        1R1/4AEN0E16sV54gVmvkoSm9UCUDM4RXDdC/YgjpVyXla7HFp2KSrTvAoGBAN+x
        VzK/7hCFod5KZXq/Nfy4/Wg/1GTwzg8eQCUbRJ3jqk0UWvNnhwWTEHfa8ywDSJFi
        bNMlTKtdlGKPHB9dGMt9izGoyeybz0RJz8aCLODN9PBr1GSAhWfqFNYgksScEOy9
        c7eEn25Q91tanmni38Y0KZU9iOYAcKJR5Xulw3WpAoGAO/3lBVNlJXTjFcmdtvFz
        4Dv52LR3Dv/1oJ2F1NXO482Nh5OBTJ401iP0XaJWJNl9kKLiaWW6g3YIrUgUn1Km
        vL9dSXN7S2HZN9UVJ3tUOPCaPcuj12bsJpvl6KGe3UtvhhnwQLR45U3Vqr8U/fRA
        PC44REUe64MMHX+OEUm+MGUCgYB/4UgyURruAxdIl0twYsOgWLk10dfAZRHH/sk4
        7V/Ky45eRlbAc9zyyOJPQrJl5PKlepkwFFDCXtsnhRzUqUo1eu4KU64sP9678V6A
        44Z4dgWjNGHVmsupXl7PEwwUrgvW62+t6HmkfVELvsB1VCgNjWCAWw9aPcImaZ9B
        ksAtEQKBgQCs2ZwTMQOX0IyBMRxVDD8JLCYMNZPBisGTjtYQv6F1ITZOzAPwJjI8
        3FzbcqCWtmbCe6rCd9p9NDU42cuizlSZfO+2emM5CnKdvb0IeHqODfBZm2vYYJ6p
        Euy/YLiXxrwHUo1KecuH04+/s6OxEzMnrYxXqvcK9SwcNTwAkDaBUw==
        -----END RSA PRIVATE KEY-----
      EOF
    end

    def default_public_key
      <<~EOF
        -----BEGIN PUBLIC KEY-----
        MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxbz0rp+Z7MklMtSkfiRf
        ceOeTOhOgkGqonCL1B0MRzSjA3yfonvEobQNYv7uyQ+ZMGT9RL7AlUSUxeWF00A/
        O6kuwfs4JlPS/FMPy/B2V0UmoteTp40LmclZHpKZs9yKmgkfa5j8Jjvd/VvV1r/D
        bkHjZetIe07pSnP3EOAG7sjyV7yrHPvkgG5h/Vn2U19vTsvYIENcj5OCLF7eUSJZ
        /6m4qem+wZ4/9cau5E2t57oS8bTd5k00Jn0E+qRVionLVLtHXKnr0nWlGPinL+Uh
        KBMhLA6Olm5Y8W77sYcUSvlJMy4GmpIvnWFKQE5vim4zKt3dBF256QPmRCWPTQ+s
        xwIDAQAB
        -----END PUBLIC KEY-----
      EOF
    end
  end

end
