| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| |
| #include "remoting/host/chromoting_host.h" |
| |
| #include <array> |
| #include <cstddef> |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/functional/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/notreached.h" |
| #include "base/process/process_handle.h" |
| #include "base/run_loop.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_future.h" |
| #include "build/build_config.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "net/base/network_change_notifier.h" |
| #include "remoting/base/auto_thread_task_runner.h" |
| #include "remoting/base/errors.h" |
| #include "remoting/base/local_session_policies_provider.h" |
| #include "remoting/base/session_policies.h" |
| #include "remoting/host/base/desktop_environment_options.h" |
| #include "remoting/host/chromoting_host_context.h" |
| #include "remoting/host/client_session.h" |
| #include "remoting/host/fake_desktop_environment.h" |
| #include "remoting/host/host_extension.h" |
| #include "remoting/host/host_mock_objects.h" |
| #include "remoting/host/mojom/chromoting_host_services.mojom.h" |
| #include "remoting/protocol/connection_to_client.h" |
| #include "remoting/protocol/fake_connection_to_client.h" |
| #include "remoting/protocol/protocol_mock_objects.h" |
| #include "remoting/protocol/session.h" |
| #include "remoting/protocol/session_config.h" |
| #include "remoting/protocol/session_manager.h" |
| #include "remoting/protocol/transport.h" |
| #include "remoting/protocol/transport_context.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if BUILDFLAG(IS_WIN) |
| #include <windows.h> |
| #endif |
| |
| using ::remoting::protocol::MockClientStub; |
| using ::remoting::protocol::MockConnectionToClientEventHandler; |
| using ::remoting::protocol::MockHostStub; |
| using ::remoting::protocol::MockSession; |
| using ::remoting::protocol::MockVideoStub; |
| using ::remoting::protocol::Session; |
| using ::remoting::protocol::SessionConfig; |
| |
| using testing::_; |
| using testing::AnyNumber; |
| using testing::AtLeast; |
| using testing::AtMost; |
| using testing::DeleteArg; |
| using testing::DoAll; |
| using testing::Expectation; |
| using testing::InSequence; |
| using testing::InvokeArgument; |
| using testing::InvokeWithoutArgs; |
| using testing::Return; |
| using testing::ReturnRef; |
| using testing::SaveArg; |
| using testing::Sequence; |
| |
| namespace remoting { |
| |
| const std::size_t kNumFailuresIgnored = 5; |
| |
| class ChromotingHostTest : public testing::Test { |
| public: |
| ChromotingHostTest() = default; |
| |
| void SetUp() override { |
| network_change_notifier_ = net::NetworkChangeNotifier::CreateIfNeeded(); |
| |
| task_runner_ = new AutoThreadTaskRunner( |
| base::SingleThreadTaskRunner::GetCurrentDefault(), base::DoNothing()); |
| |
| desktop_environment_factory_ = |
| std::make_unique<FakeDesktopEnvironmentFactory>( |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| session_manager_ = new protocol::MockSessionManager(); |
| |
| host_ = std::make_unique<ChromotingHost>( |
| desktop_environment_factory_.get(), |
| base::WrapUnique(session_manager_.get()), |
| protocol::TransportContext::ForTests(protocol::TransportRole::SERVER), |
| task_runner_, // Audio |
| task_runner_, // Video encode |
| DesktopEnvironmentOptions::CreateDefault(), base::NullCallback(), |
| &local_session_policies_provider_); |
| host_->status_monitor()->AddStatusObserver(&host_status_observer_); |
| |
| owner_email_ = "host@domain"; |
| session1_ = new MockSession(); |
| session2_ = new MockSession(); |
| session_unowned1_ = std::make_unique<MockSession>(); |
| session_unowned2_ = std::make_unique<MockSession>(); |
| session_config1_ = SessionConfig::ForTest(); |
| session_jid1_ = "user@domain/rest-of-jid"; |
| session_config2_ = SessionConfig::ForTest(); |
| session_jid2_ = "user2@domain/rest-of-jid"; |
| session_unowned_jid1_ = "user3@doman/rest-of-jid"; |
| session_unowned_jid2_ = "user4@doman/rest-of-jid"; |
| |
| EXPECT_CALL(*session1_, jid()).WillRepeatedly(ReturnRef(session_jid1_)); |
| EXPECT_CALL(*session2_, jid()).WillRepeatedly(ReturnRef(session_jid2_)); |
| EXPECT_CALL(*session_unowned1_, jid()) |
| .WillRepeatedly(ReturnRef(session_unowned_jid1_)); |
| EXPECT_CALL(*session_unowned2_, jid()) |
| .WillRepeatedly(ReturnRef(session_unowned_jid2_)); |
| EXPECT_CALL(*session_unowned1_, SetEventHandler(_)) |
| .Times(AnyNumber()) |
| .WillRepeatedly(SaveArg<0>(&session_unowned1_event_handler_)); |
| EXPECT_CALL(*session_unowned2_, SetEventHandler(_)) |
| .Times(AnyNumber()) |
| .WillRepeatedly(SaveArg<0>(&session_unowned2_event_handler_)); |
| EXPECT_CALL(*session1_, config()) |
| .WillRepeatedly(ReturnRef(*session_config1_)); |
| EXPECT_CALL(*session2_, config()) |
| .WillRepeatedly(ReturnRef(*session_config2_)); |
| EXPECT_CALL(*session_unowned1_, config()) |
| .WillRepeatedly(ReturnRef(*session_config1_)); |
| EXPECT_CALL(*session_unowned2_, config()) |
| .WillRepeatedly(ReturnRef(*session_config2_)); |
| |
| connection1_ = std::make_unique<protocol::FakeConnectionToClient>( |
| base::WrapUnique(session1_.get())); |
| connection1_->set_host_stub(&host_stub1_); |
| connection1_->set_client_stub(&client_stub1_); |
| |
| connection2_ = std::make_unique<protocol::FakeConnectionToClient>( |
| base::WrapUnique(session2_.get())); |
| connection2_->set_host_stub(&host_stub2_); |
| connection2_->set_client_stub(&client_stub2_); |
| } |
| |
| // Helper method to pretend a client is connected to ChromotingHost. |
| void SimulateClientConnection(int connection_index, |
| bool authenticate, |
| bool reject) { |
| std::unique_ptr<protocol::ConnectionToClient> connection = |
| std::move((connection_index == 0) ? connection1_ : connection2_); |
| protocol::ConnectionToClient* connection_ptr = connection.get(); |
| auto client = std::make_unique<ClientSession>( |
| host_.get(), std::move(connection), desktop_environment_factory_.get(), |
| DesktopEnvironmentOptions::CreateDefault(), nullptr, |
| std::vector<raw_ptr<HostExtension, VectorExperimental>>(), |
| &local_session_policies_provider_); |
| ClientSession* client_ptr = client.get(); |
| |
| connection_ptr->set_host_stub(client.get()); |
| get_client(connection_index) = client_ptr; |
| |
| // |host| is responsible for deleting |client| from now on. |
| host_->clients_.push_back(std::move(client)); |
| |
| if (authenticate) { |
| if (reject) { |
| // Free the corresponding client pointer to prevent a dangling pointer |
| // crash. |
| PrepareForClientDisconnection(connection_index); |
| } |
| client_ptr->OnConnectionAuthenticated(nullptr); |
| if (!reject) { |
| client_ptr->OnConnectionChannelsConnected(); |
| } |
| } else { |
| PrepareForClientDisconnection(connection_index); |
| client_ptr->OnConnectionClosed(ErrorCode::AUTHENTICATION_FAILED); |
| } |
| } |
| |
| void TearDown() override { |
| if (host_) { |
| ShutdownHost(); |
| } |
| task_runner_ = nullptr; |
| |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void NotifyConnectionClosed1() { |
| if (session_unowned1_event_handler_) { |
| protocol::Session::EventHandler* handler = |
| std::exchange(session_unowned1_event_handler_, nullptr); |
| handler->OnSessionStateChange(Session::CLOSED); |
| } |
| } |
| |
| void NotifyConnectionClosed2() { |
| if (session_unowned2_event_handler_) { |
| protocol::Session::EventHandler* handler = |
| std::exchange(session_unowned2_event_handler_, nullptr); |
| handler->OnSessionStateChange(Session::CLOSED); |
| } |
| } |
| |
| void ShutdownHost() { |
| EXPECT_CALL(host_status_observer_, OnHostShutdown()); |
| session_manager_ = nullptr; |
| client1_ = nullptr; |
| session1_ = nullptr; |
| client2_ = nullptr; |
| session2_ = nullptr; |
| host_.reset(); |
| desktop_environment_factory_.reset(); |
| } |
| |
| // Starts the host. |
| void StartHost() { |
| EXPECT_CALL(host_status_observer_, OnHostStarted(owner_email_)); |
| EXPECT_CALL(*session_manager_, AcceptIncoming(_)); |
| host_->Start(owner_email_); |
| } |
| |
| // Expect a client to connect. |
| // Return an expectation that a session has started. |
| std::unique_ptr<base::test::TestFuture<void>> ExpectClientConnected( |
| int connection_index) { |
| const std::string& session_jid = get_session_jid(connection_index); |
| auto future = std::make_unique<base::test::TestFuture<void>>(); |
| |
| Expectation client_authenticated = |
| EXPECT_CALL(host_status_observer_, OnClientAuthenticated(session_jid)); |
| EXPECT_CALL(host_status_observer_, OnClientConnected(session_jid)) |
| .After(client_authenticated) |
| .WillOnce([callback = future->GetCallback()]() mutable { |
| std::move(callback).Run(); |
| }); |
| return future; |
| } |
| |
| ClientSession* PrepareForClientDisconnection(int connection_index) { |
| // A client disconnecting will destroy the session and client. |
| // Clear both the session and client and return the client to the caller. |
| switch (connection_index) { |
| case 0: |
| session1_ = nullptr; |
| return std::exchange(client1_, nullptr); |
| case 1: |
| session2_ = nullptr; |
| return std::exchange(client2_, nullptr); |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| // Expect that a client is disconnected. The given action will be done after |
| // the status observer is notified that the session has finished. |
| // A pointer to the client is returned. |
| ClientSession* ExpectClientDisconnected(int connection_index) { |
| EXPECT_CALL(host_status_observer_, |
| OnClientDisconnected(get_session_jid(connection_index))) |
| .RetiresOnSaturation(); |
| return PrepareForClientDisconnection(connection_index); |
| } |
| |
| void SetPerSessionPoliciesValidator( |
| const ChromotingHost::SessionPoliciesValidator& validator) { |
| host_->per_session_policies_validator_ = validator; |
| } |
| |
| mojo::Remote<mojom::ChromotingHostServices> BindChromotingHostServices() { |
| mojo::Remote<mojom::ChromotingHostServices> remote; |
| // ChromotingHost::BindSessionServices calls ProcessIdToSessionId() on the |
| // IPC client's PID. The PID we know that always works is the current |
| // process' PID. |
| auto current_pid = base::GetCurrentProcId(); |
| host_->BindChromotingHostServices(remote.BindNewPipeAndPassReceiver(), |
| current_pid); |
| return remote; |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| // Simulates the IPC client's session ID for the session ID check in |
| // ChromotingHost::BindSessionServices. |
| // |
| // |is_remote_desktop_session_id|: True if the simulated session ID should be |
| // exactly the session ID of the fake desktop environment. If false, the |
| // simulated session ID is guaranteed to be different from the desktop |
| // environment's session ID. |
| void SimulateIpcClientSessionId(bool is_remote_desktop_session_id) { |
| // ChromotingHost::BindSessionServices calls ProcessIdToSessionId() on the |
| // IPC client's PID. The PID we know that always works is the current |
| // process' PID. |
| auto current_pid = base::GetCurrentProcId(); |
| DWORD current_session_id; |
| bool success = ProcessIdToSessionId(current_pid, ¤t_session_id); |
| ASSERT_TRUE(success); |
| // The IPC client's session ID is exactly the current process' session ID |
| // at this point, so we change the fake desktop environment's session ID |
| // here. |
| if (is_remote_desktop_session_id) { |
| desktop_environment_factory_->set_desktop_session_id(current_session_id); |
| } else { |
| desktop_environment_factory_->set_desktop_session_id(current_session_id + |
| 1); |
| } |
| } |
| #endif |
| |
| protected: |
| base::test::TaskEnvironment task_environment_; |
| std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier_; |
| scoped_refptr<AutoThreadTaskRunner> task_runner_; |
| MockConnectionToClientEventHandler handler_; |
| std::unique_ptr<FakeDesktopEnvironmentFactory> desktop_environment_factory_; |
| MockHostStatusObserver host_status_observer_; |
| LocalSessionPoliciesProvider local_session_policies_provider_; |
| std::unique_ptr<ChromotingHost> host_; |
| raw_ptr<protocol::MockSessionManager> session_manager_; |
| std::string owner_email_; |
| std::unique_ptr<protocol::FakeConnectionToClient> connection1_; |
| raw_ptr<ClientSession> client1_; // Owned by |host_|. |
| std::string session_jid1_; |
| raw_ptr<MockSession> session1_; // Owned by |connection1_|. |
| std::unique_ptr<SessionConfig> session_config1_; |
| MockClientStub client_stub1_; |
| MockHostStub host_stub1_; |
| std::unique_ptr<protocol::FakeConnectionToClient> connection2_; |
| raw_ptr<ClientSession> client2_; // Owned by |host_|. |
| std::string session_jid2_; |
| raw_ptr<MockSession> session2_; // Owned by |connection2_|. |
| std::unique_ptr<SessionConfig> session_config2_; |
| MockClientStub client_stub2_; |
| MockHostStub host_stub2_; |
| std::unique_ptr<MockSession> session_unowned1_; // Not owned by a connection. |
| std::string session_unowned_jid1_; |
| std::unique_ptr<MockSession> session_unowned2_; // Not owned by a connection. |
| std::string session_unowned_jid2_; |
| raw_ptr<protocol::Session::EventHandler> session_unowned1_event_handler_; |
| raw_ptr<protocol::Session::EventHandler> session_unowned2_event_handler_; |
| |
| // Returns the cached client pointers client1_ or client2_. |
| raw_ptr<ClientSession>& get_client(int connection_index) { |
| return (connection_index == 0) ? client1_ : client2_; |
| } |
| |
| const std::string& get_session_jid(int connection_index) { |
| return (connection_index == 0) ? session_jid1_ : session_jid2_; |
| } |
| }; |
| |
| TEST_F(ChromotingHostTest, StartAndShutdown) { |
| StartHost(); |
| } |
| |
| TEST_F(ChromotingHostTest, Connect) { |
| StartHost(); |
| |
| // Shut down the host when the first video packet is received. |
| auto future = ExpectClientConnected(0); |
| SimulateClientConnection(0, true, false); |
| future->Get(); |
| } |
| |
| TEST_F(ChromotingHostTest, AuthenticationFailed) { |
| StartHost(); |
| |
| EXPECT_CALL(host_status_observer_, OnClientAccessDenied(session_jid1_)); |
| SimulateClientConnection(0, false, false); |
| } |
| |
| TEST_F(ChromotingHostTest, Reconnect) { |
| StartHost(); |
| |
| // Connect first client. |
| auto future = ExpectClientConnected(0); |
| SimulateClientConnection(0, true, false); |
| future->Get(); |
| |
| // Disconnect first client. |
| ClientSession* client1 = ExpectClientDisconnected(0); |
| client1->OnConnectionClosed(ErrorCode::OK); |
| |
| // Connect second client. |
| future = ExpectClientConnected(1); |
| SimulateClientConnection(1, true, false); |
| future->Get(); |
| |
| // Disconnect second client. |
| ClientSession* client2 = ExpectClientDisconnected(1); |
| client2->OnConnectionClosed(ErrorCode::OK); |
| } |
| |
| TEST_F(ChromotingHostTest, ConnectWhenAnotherClientIsConnected) { |
| StartHost(); |
| |
| // Connect first client. |
| auto future = ExpectClientConnected(0); |
| SimulateClientConnection(0, true, false); |
| future->Get(); |
| |
| // Connect second client. First client should be disconnected automatically. |
| { |
| InSequence s; |
| ExpectClientDisconnected(0); |
| future = ExpectClientConnected(1); |
| } |
| SimulateClientConnection(1, true, false); |
| future->Get(); |
| |
| // Disconnect second client. |
| ClientSession* client2 = ExpectClientDisconnected(1); |
| client2->OnConnectionClosed(ErrorCode::OK); |
| } |
| |
| TEST_F(ChromotingHostTest, IncomingSessionAccepted) { |
| StartHost(); |
| |
| MockSession* session = session_unowned1_.get(); |
| protocol::SessionManager::IncomingSessionResponse response = |
| protocol::SessionManager::DECLINE; |
| std::string rejection_reason; |
| base::Location rejection_location; |
| host_->OnIncomingSession(session_unowned1_.release(), &response, |
| &rejection_reason, &rejection_location); |
| EXPECT_EQ(protocol::SessionManager::ACCEPT, response); |
| EXPECT_TRUE(rejection_reason.empty()); |
| EXPECT_EQ(nullptr, rejection_location.program_counter()); |
| |
| EXPECT_CALL(*session, Close(_, _, _)) |
| .WillOnce(InvokeWithoutArgs( |
| this, &ChromotingHostTest::NotifyConnectionClosed1)); |
| ShutdownHost(); |
| } |
| |
| TEST_F(ChromotingHostTest, LoginBackOffTriggersIfClientsDoNotAuthenticate) { |
| StartHost(); |
| |
| protocol::SessionManager::IncomingSessionResponse response = |
| protocol::SessionManager::DECLINE; |
| std::string rejection_reason; |
| base::Location rejection_location; |
| std::array<protocol::Session::EventHandler*, kNumFailuresIgnored + 1> |
| session_event_handlers; |
| for (auto*& session_event_handler : session_event_handlers) { |
| // Set expectations and responses for the new session. |
| auto session = std::make_unique<MockSession>(); |
| EXPECT_CALL(*session, jid()).WillRepeatedly(ReturnRef(session_jid1_)); |
| EXPECT_CALL(*session, config()) |
| .WillRepeatedly(ReturnRef(*session_config1_)); |
| EXPECT_CALL(*session, SetEventHandler(_)) |
| .Times(AnyNumber()) |
| .WillRepeatedly(SaveArg<0>(&session_event_handler)); |
| EXPECT_CALL(*session, Close(_, _, _)) |
| .WillOnce(InvokeWithoutArgs([&session_event_handler]() { |
| session_event_handler->OnSessionStateChange(Session::CLOSED); |
| })); |
| // Simulate the incoming connection. |
| host_->OnIncomingSession(session.release(), &response, &rejection_reason, |
| &rejection_location); |
| EXPECT_EQ(protocol::SessionManager::ACCEPT, response); |
| EXPECT_TRUE(rejection_reason.empty()); |
| EXPECT_EQ(nullptr, rejection_location.program_counter()); |
| // Begin authentication; this will increase the backoff count, and since |
| // OnSessionAuthenticated is never called, the host should only allow |
| // kNumFailuresIgnored + 1 connections before beginning the backoff. |
| host_->OnSessionAuthenticating( |
| host_->client_sessions_for_tests().front().get()); |
| } |
| |
| // As this is connection kNumFailuresIgnored + 2, it should be rejected. |
| host_->OnIncomingSession(session_unowned2_.get(), &response, |
| &rejection_reason, &rejection_location); |
| EXPECT_EQ(protocol::SessionManager::OVERLOAD, response); |
| EXPECT_FALSE(rejection_reason.empty()); |
| EXPECT_NE(nullptr, rejection_location.program_counter()); |
| EXPECT_EQ(host_->client_sessions_for_tests().size(), kNumFailuresIgnored + 1); |
| |
| // Shut down host while objects owned by this test are still in scope. |
| ShutdownHost(); |
| } |
| |
| TEST_F(ChromotingHostTest, LoginBackOffResetsIfClientsAuthenticate) { |
| StartHost(); |
| |
| protocol::SessionManager::IncomingSessionResponse response = |
| protocol::SessionManager::DECLINE; |
| std::string rejection_reason; |
| base::Location rejection_location; |
| std::array<protocol::Session::EventHandler*, kNumFailuresIgnored + 1> |
| session_event_handlers; |
| for (auto*& session_event_handler : session_event_handlers) { |
| // Set expectations and responses for the new session. |
| auto session = std::make_unique<MockSession>(); |
| EXPECT_CALL(*session, jid()).WillRepeatedly(ReturnRef(session_jid1_)); |
| EXPECT_CALL(*session, config()) |
| .WillRepeatedly(ReturnRef(*session_config1_)); |
| EXPECT_CALL(*session, SetEventHandler(_)) |
| .Times(AnyNumber()) |
| .WillRepeatedly(SaveArg<0>(&session_event_handler)); |
| EXPECT_CALL(*session, Close(_, _, _)) |
| .WillOnce(InvokeWithoutArgs([&session_event_handler]() { |
| session_event_handler->OnSessionStateChange(Session::CLOSED); |
| })); |
| // Simulate the incoming connection. |
| host_->OnIncomingSession(session.release(), &response, &rejection_reason, |
| &rejection_location); |
| EXPECT_EQ(protocol::SessionManager::ACCEPT, response); |
| EXPECT_TRUE(rejection_reason.empty()); |
| EXPECT_EQ(nullptr, rejection_location.program_counter()); |
| // Begin authentication; this will increase the backoff count |
| host_->OnSessionAuthenticating( |
| host_->client_sessions_for_tests().front().get()); |
| } |
| |
| // Simulate successful authentication for one of the previous connections. |
| // This should reset the backoff and disconnect all the other connections. |
| host_->OnSessionAuthenticated( |
| host_->client_sessions_for_tests().front().get()); |
| EXPECT_EQ(host_->client_sessions_for_tests().size(), 1U); |
| |
| // This is connection kNumFailuresIgnored + 2, but since we now have a |
| // successful authentication it should not be rejected. |
| auto session = std::make_unique<MockSession>(); |
| protocol::Session::EventHandler* session_event_handler; |
| EXPECT_CALL(*session, jid()).WillRepeatedly(ReturnRef(session_jid1_)); |
| EXPECT_CALL(*session, config()).WillRepeatedly(ReturnRef(*session_config1_)); |
| EXPECT_CALL(*session, SetEventHandler(_)) |
| .Times(AnyNumber()) |
| .WillRepeatedly(SaveArg<0>(&session_event_handler)); |
| EXPECT_CALL(*session, Close(_, _, _)) |
| .WillOnce(InvokeWithoutArgs([&session_event_handler]() { |
| session_event_handler->OnSessionStateChange(Session::CLOSED); |
| })); |
| host_->OnIncomingSession(session.release(), &response, &rejection_reason, |
| &rejection_location); |
| EXPECT_EQ(protocol::SessionManager::ACCEPT, response); |
| EXPECT_TRUE(rejection_reason.empty()); |
| EXPECT_EQ(nullptr, rejection_location.program_counter()); |
| |
| // Shut down host while objects owned by this test are still in scope. |
| ShutdownHost(); |
| } |
| |
| // Flaky on all platforms. http://crbug.com/1265894 |
| TEST_F(ChromotingHostTest, DISABLED_OnSessionRouteChange) { |
| StartHost(); |
| |
| ExpectClientConnected(0); |
| SimulateClientConnection(0, true, false); |
| |
| std::string channel_name("ChannelName"); |
| protocol::TransportRoute route; |
| EXPECT_CALL(host_status_observer_, |
| OnClientRouteChange(session_jid1_, channel_name, _)); |
| host_->OnSessionRouteChange(get_client(0), channel_name, route); |
| } |
| |
| TEST_F(ChromotingHostTest, ExtraSessionPoliciesValidator) { |
| SessionPolicies session_policies; |
| session_policies.host_username_match_required = true; |
| local_session_policies_provider_.set_local_policies(session_policies); |
| base::MockCallback<ChromotingHost::SessionPoliciesValidator> mock_validator; |
| EXPECT_CALL(mock_validator, Run(session_policies)) |
| .WillOnce(Return(ErrorCode::DISALLOWED_BY_POLICY)); |
| SetPerSessionPoliciesValidator(mock_validator.Get()); |
| |
| StartHost(); |
| |
| EXPECT_CALL(host_status_observer_, OnClientDisconnected(get_session_jid(0))); |
| |
| SimulateClientConnection(0, /* authenticate= */ true, /* reject= */ true); |
| } |
| |
| TEST_F(ChromotingHostTest, BindSessionServicesWithNoConnectedSession_Rejected) { |
| StartHost(); |
| |
| mojo::Remote<mojom::ChromotingSessionServices> remote; |
| auto receiver = remote.BindNewPipeAndPassReceiver(); |
| base::RunLoop wait_for_disconnect_run_loop; |
| remote.set_disconnect_handler(wait_for_disconnect_run_loop.QuitClosure()); |
| host_->BindSessionServices(std::move(receiver)); |
| wait_for_disconnect_run_loop.Run(); |
| } |
| |
| TEST_F(ChromotingHostTest, BindSessionServicesWithConnectedSession_Accepted) { |
| StartHost(); |
| auto host_services_remote = BindChromotingHostServices(); |
| #if BUILDFLAG(IS_WIN) |
| SimulateIpcClientSessionId(/* is_remote_desktop_session_id= */ true); |
| #endif |
| auto future = ExpectClientConnected(0); |
| SimulateClientConnection(0, true, false); |
| future->Get(); |
| |
| mojo::Remote<mojom::ChromotingSessionServices> remote; |
| auto receiver = remote.BindNewPipeAndPassReceiver(); |
| base::RunLoop wait_for_version_run_loop; |
| remote.set_disconnect_handler(base::BindLambdaForTesting([&]() { |
| wait_for_version_run_loop.Quit(); |
| FAIL() << "Disconnect handler should not be called."; |
| })); |
| // QueryVersion() is used to determine whether the server accepts the bind |
| // request; if it doesn't, the callback won't be called, and the disconnect |
| // handler will be called instead. |
| remote.QueryVersion(base::BindLambdaForTesting( |
| [&](std::uint32_t version) { wait_for_version_run_loop.Quit(); })); |
| // Note that we can't just call host_->BindSessionServices(), since that |
| // doesn't have the peer PID context. |
| host_services_remote->BindSessionServices(std::move(receiver)); |
| wait_for_version_run_loop.Run(); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| TEST_F(ChromotingHostTest, BindSessionServicesWithWrongSession_Rejected) { |
| StartHost(); |
| auto host_services_remote = BindChromotingHostServices(); |
| SimulateIpcClientSessionId(/* is_remote_desktop_session_id= */ false); |
| auto future = ExpectClientConnected(0); |
| SimulateClientConnection(0, true, false); |
| future->Get(); |
| |
| mojo::Remote<mojom::ChromotingSessionServices> remote; |
| auto receiver = remote.BindNewPipeAndPassReceiver(); |
| base::RunLoop wait_for_disconnect_run_loop; |
| remote.set_disconnect_handler(wait_for_disconnect_run_loop.QuitClosure()); |
| // Note that we can't just call host_->BindSessionServices(), since that |
| // doesn't have the peer PID context. |
| host_services_remote->BindSessionServices(std::move(receiver)); |
| wait_for_disconnect_run_loop.Run(); |
| } |
| #endif |
| |
| } // namespace remoting |