| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/guid.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/process/process.h" |
| #include "base/run_loop.h" |
| #include "base/test/test_suite.h" |
| #include "mojo/public/cpp/bindings/binding_set.h" |
| #include "services/service_manager/public/cpp/binder_registry.h" |
| #include "services/service_manager/public/cpp/interface_factory.h" |
| #include "services/service_manager/public/cpp/service_test.h" |
| #include "services/service_manager/public/interfaces/service_manager.mojom.h" |
| #include "services/service_manager/tests/connect/connect_test.mojom.h" |
| #include "services/service_manager/tests/util.h" |
| |
| // Tests that multiple services can be packaged in a single service by |
| // implementing ServiceFactory; that these services can be specified by |
| // the package's manifest and are thus registered with the PackageManager. |
| |
| namespace service_manager { |
| |
| namespace { |
| |
| const char kTestPackageName[] = "connect_test_package"; |
| const char kTestAppName[] = "connect_test_app"; |
| const char kTestAppAName[] = "connect_test_a"; |
| const char kTestAppBName[] = "connect_test_b"; |
| const char kTestClassAppName[] = "connect_test_class_app"; |
| const char kTestSingletonAppName[] = "connect_test_singleton_app"; |
| |
| void ReceiveOneString(std::string* out_string, |
| base::RunLoop* loop, |
| const std::string& in_string) { |
| *out_string = in_string; |
| loop->Quit(); |
| } |
| |
| void ReceiveTwoStrings(std::string* out_string_1, |
| std::string* out_string_2, |
| base::RunLoop* loop, |
| const std::string& in_string_1, |
| const std::string& in_string_2) { |
| *out_string_1 = in_string_1; |
| *out_string_2 = in_string_2; |
| loop->Quit(); |
| } |
| |
| void ReceiveConnectionResult(mojom::ConnectResult* out_result, |
| Identity* out_target, |
| base::RunLoop* loop, |
| int32_t in_result, |
| const service_manager::Identity& in_identity) { |
| *out_result = static_cast<mojom::ConnectResult>(in_result); |
| *out_target = in_identity; |
| loop->Quit(); |
| } |
| |
| void StartServiceResponse(base::RunLoop* quit_loop, |
| mojom::ConnectResult* out_result, |
| Identity* out_resolved_identity, |
| mojom::ConnectResult result, |
| const Identity& resolved_identity) { |
| if (quit_loop) |
| quit_loop->Quit(); |
| if (out_result) |
| *out_result = result; |
| if (out_resolved_identity) |
| *out_resolved_identity = resolved_identity; |
| } |
| |
| void QuitLoop(base::RunLoop* loop) { |
| loop->Quit(); |
| } |
| |
| } // namespace |
| |
| class ConnectTest : public test::ServiceTest, |
| public InterfaceFactory<test::mojom::ExposedInterface>, |
| public test::mojom::ExposedInterface { |
| public: |
| ConnectTest() : ServiceTest("connect_unittests") {} |
| ~ConnectTest() override {} |
| |
| protected: |
| void CompareConnectionState( |
| const std::string& connection_local_name, |
| const std::string& connection_remote_name, |
| const std::string& connection_remote_userid, |
| const std::string& initialize_local_name, |
| const std::string& initialize_userid) { |
| EXPECT_EQ(connection_remote_name, |
| connection_state_->connection_remote_name); |
| EXPECT_EQ(connection_remote_userid, |
| connection_state_->connection_remote_userid); |
| EXPECT_EQ(initialize_local_name, connection_state_->initialize_local_name); |
| EXPECT_EQ(initialize_userid, connection_state_->initialize_userid); |
| } |
| |
| private: |
| class TestService : public test::ServiceTestClient { |
| public: |
| explicit TestService(ConnectTest* connect_test) |
| : test::ServiceTestClient(connect_test), connect_test_(connect_test) { |
| registry_.AddInterface<test::mojom::ExposedInterface>(connect_test_); |
| } |
| ~TestService() override {} |
| |
| private: |
| void OnBindInterface( |
| const ServiceInfo& source_info, |
| const std::string& interface_name, |
| mojo::ScopedMessagePipeHandle interface_pipe) override { |
| registry_.BindInterface(source_info.identity, interface_name, |
| std::move(interface_pipe)); |
| } |
| |
| ConnectTest* connect_test_; |
| BinderRegistry registry_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestService); |
| }; |
| |
| // test::ServiceTest: |
| void SetUp() override { |
| test::ServiceTest::SetUp(); |
| // We need to connect to the package first to force the service manager to |
| // read the |
| // package app's manifest and register aliases for the applications it |
| // provides. |
| test::mojom::ConnectTestServicePtr root_service; |
| connector()->BindInterface(kTestPackageName, &root_service); |
| base::RunLoop run_loop; |
| std::string root_name; |
| root_service->GetTitle( |
| base::Bind(&ReceiveOneString, &root_name, &run_loop)); |
| run_loop.Run(); |
| } |
| std::unique_ptr<Service> CreateService() override { |
| return base::MakeUnique<TestService>(this); |
| } |
| |
| // InterfaceFactory<test::mojom::ExposedInterface>: |
| void Create(const Identity& remote_identity, |
| test::mojom::ExposedInterfaceRequest request) override { |
| bindings_.AddBinding(this, std::move(request)); |
| } |
| |
| void ConnectionAccepted(test::mojom::ConnectionStatePtr state) override { |
| connection_state_ = std::move(state); |
| } |
| |
| test::mojom::ConnectionStatePtr connection_state_; |
| |
| mojo::BindingSet<test::mojom::ExposedInterface> bindings_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ConnectTest); |
| }; |
| |
| // Ensure the connection was properly established and that a round trip |
| // method call/response is completed. |
| TEST_F(ConnectTest, BindInterface) { |
| test::mojom::ConnectTestServicePtr service; |
| connector()->BindInterface(kTestAppName, &service); |
| base::RunLoop run_loop; |
| std::string title; |
| service->GetTitle(base::Bind(&ReceiveOneString, &title, &run_loop)); |
| run_loop.Run(); |
| EXPECT_EQ("APP", title); |
| } |
| |
| TEST_F(ConnectTest, Instances) { |
| Identity identity_a(kTestAppName, mojom::kInheritUserID, "A"); |
| std::string instance_a1, instance_a2; |
| test::mojom::ConnectTestServicePtr service_a1; |
| { |
| connector()->BindInterface(identity_a, &service_a1); |
| base::RunLoop loop; |
| service_a1->GetInstance(base::Bind(&ReceiveOneString, &instance_a1, &loop)); |
| loop.Run(); |
| } |
| test::mojom::ConnectTestServicePtr service_a2; |
| { |
| connector()->BindInterface(identity_a, &service_a2); |
| base::RunLoop loop; |
| service_a2->GetInstance(base::Bind(&ReceiveOneString, &instance_a2, &loop)); |
| loop.Run(); |
| } |
| EXPECT_EQ(instance_a1, instance_a2); |
| |
| Identity identity_b(kTestAppName, mojom::kInheritUserID, "B"); |
| std::string instance_b; |
| test::mojom::ConnectTestServicePtr service_b; |
| { |
| connector()->BindInterface(identity_b, &service_b); |
| base::RunLoop loop; |
| service_b->GetInstance(base::Bind(&ReceiveOneString, &instance_b, &loop)); |
| loop.Run(); |
| } |
| |
| EXPECT_NE(instance_a1, instance_b); |
| } |
| |
| // BlockedInterface should not be exposed to this application because it is not |
| // in our CapabilityFilter whitelist. |
| TEST_F(ConnectTest, BlockedInterface) { |
| base::RunLoop run_loop; |
| test::mojom::BlockedInterfacePtr blocked; |
| connector()->BindInterface(kTestAppName, &blocked); |
| blocked.set_connection_error_handler(base::Bind(&QuitLoop, &run_loop)); |
| std::string title = "unchanged"; |
| blocked->GetTitleBlocked(base::Bind(&ReceiveOneString, &title, &run_loop)); |
| run_loop.Run(); |
| EXPECT_EQ("unchanged", title); |
| } |
| |
| // Connects to an app provided by a package. |
| TEST_F(ConnectTest, PackagedApp) { |
| test::mojom::ConnectTestServicePtr service_a; |
| connector()->BindInterface(kTestAppAName, &service_a); |
| Connector::TestApi test_api(connector()); |
| Identity resolved_identity; |
| test_api.SetStartServiceCallback( |
| base::Bind(&StartServiceResponse, nullptr, nullptr, &resolved_identity)); |
| base::RunLoop run_loop; |
| std::string a_name; |
| service_a->GetTitle(base::Bind(&ReceiveOneString, &a_name, &run_loop)); |
| run_loop.Run(); |
| EXPECT_EQ("A", a_name); |
| EXPECT_EQ(resolved_identity.name(), kTestAppAName); |
| } |
| |
| // Ask the target application to attempt to connect to a third application |
| // provided by a package whose id is permitted by the primary target's |
| // CapabilityFilter but whose package is not. The connection should be |
| // allowed regardless of the target's CapabilityFilter with respect to the |
| // package. |
| TEST_F(ConnectTest, BlockedPackage) { |
| test::mojom::StandaloneAppPtr standalone_app; |
| connector()->BindInterface(kTestAppName, &standalone_app); |
| base::RunLoop run_loop; |
| std::string title; |
| standalone_app->ConnectToAllowedAppInBlockedPackage( |
| base::Bind(&ReceiveOneString, &title, &run_loop)); |
| run_loop.Run(); |
| EXPECT_EQ("A", title); |
| } |
| |
| // BlockedInterface should not be exposed to this application because it is not |
| // in our CapabilityFilter whitelist. |
| TEST_F(ConnectTest, PackagedApp_BlockedInterface) { |
| base::RunLoop run_loop; |
| test::mojom::BlockedInterfacePtr blocked; |
| connector()->BindInterface(kTestAppAName, &blocked); |
| blocked.set_connection_error_handler(base::Bind(&QuitLoop, &run_loop)); |
| run_loop.Run(); |
| } |
| |
| // Connection to another application provided by the same package, blocked |
| // because it's not in the capability filter whitelist. |
| TEST_F(ConnectTest, BlockedPackagedApplication) { |
| test::mojom::ConnectTestServicePtr service_b; |
| connector()->BindInterface(kTestAppBName, &service_b); |
| Connector::TestApi test_api(connector()); |
| mojom::ConnectResult result; |
| test_api.SetStartServiceCallback( |
| base::Bind(&StartServiceResponse, nullptr, &result, nullptr)); |
| base::RunLoop run_loop; |
| service_b.set_connection_error_handler(run_loop.QuitClosure()); |
| run_loop.Run(); |
| EXPECT_EQ(mojom::ConnectResult::ACCESS_DENIED, result); |
| } |
| |
| TEST_F(ConnectTest, CapabilityClasses) { |
| test::mojom::StandaloneAppPtr standalone_app; |
| connector()->BindInterface(kTestAppName, &standalone_app); |
| std::string string1, string2; |
| base::RunLoop loop; |
| standalone_app->ConnectToClassInterface( |
| base::Bind(&ReceiveTwoStrings, &string1, &string2, &loop)); |
| loop.Run(); |
| EXPECT_EQ("PONG", string1); |
| EXPECT_EQ("CLASS APP", string2); |
| } |
| |
| TEST_F(ConnectTest, ConnectWithoutExplicitClassBlocked) { |
| // We not be able to bind a ClassInterfacePtr since the connect_unittest app |
| // does not explicitly request the "class" capability from |
| // connect_test_class_app. This test will hang if it is bound. |
| test::mojom::ClassInterfacePtr class_interface; |
| connector()->BindInterface(kTestClassAppName, &class_interface); |
| base::RunLoop loop; |
| class_interface.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); |
| loop.Run(); |
| } |
| |
| TEST_F(ConnectTest, ConnectAsDifferentUser_Allowed) { |
| test::mojom::UserIdTestPtr user_id_test; |
| connector()->BindInterface(kTestAppName, &user_id_test); |
| mojom::ConnectResult result; |
| Identity target(kTestClassAppName, base::GenerateGUID()); |
| Identity result_identity; |
| { |
| base::RunLoop loop; |
| user_id_test->ConnectToClassAppAsDifferentUser( |
| target, |
| base::Bind(&ReceiveConnectionResult, &result, &result_identity, &loop)); |
| loop.Run(); |
| } |
| EXPECT_EQ(result, mojom::ConnectResult::SUCCEEDED); |
| EXPECT_EQ(target, result_identity); |
| } |
| |
| TEST_F(ConnectTest, ConnectAsDifferentUser_Blocked) { |
| test::mojom::UserIdTestPtr user_id_test; |
| connector()->BindInterface(kTestAppAName, &user_id_test); |
| mojom::ConnectResult result; |
| Identity target(kTestClassAppName, base::GenerateGUID()); |
| Identity result_identity; |
| { |
| base::RunLoop loop; |
| user_id_test->ConnectToClassAppAsDifferentUser( |
| target, |
| base::Bind(&ReceiveConnectionResult, &result, &result_identity, &loop)); |
| loop.Run(); |
| } |
| EXPECT_EQ(mojom::ConnectResult::ACCESS_DENIED, result); |
| EXPECT_FALSE(target == result_identity); |
| } |
| |
| // There are various other tests (service manager, lifecycle) that test valid |
| // client |
| // process specifications. This is the only one for blocking. |
| TEST_F(ConnectTest, ConnectToClientProcess_Blocked) { |
| base::Process process; |
| mojom::ConnectResult result = |
| service_manager::test::LaunchAndConnectToProcess( |
| #if defined(OS_WIN) |
| "connect_test_exe.exe", |
| #else |
| "connect_test_exe", |
| #endif |
| service_manager::Identity("connect_test_exe", |
| service_manager::mojom::kInheritUserID), |
| connector(), &process); |
| EXPECT_EQ(result, mojom::ConnectResult::ACCESS_DENIED); |
| } |
| |
| // Verifies that a client with the "all_users" capability class can receive |
| // connections from clients run as other users. |
| TEST_F(ConnectTest, AllUsersSingleton) { |
| // Connect to an instance with an explicitly different user_id. This supplied |
| // user id should be ignored by the service manager (which will generate its |
| // own |
| // synthetic user id for all-user singleton instances). |
| const std::string singleton_userid = base::GenerateGUID(); |
| Identity singleton_id(kTestSingletonAppName, singleton_userid); |
| connector()->StartService(singleton_id); |
| Identity first_resolved_identity; |
| { |
| base::RunLoop loop; |
| Connector::TestApi test_api(connector()); |
| test_api.SetStartServiceCallback(base::Bind( |
| &StartServiceResponse, &loop, nullptr, &first_resolved_identity)); |
| loop.Run(); |
| EXPECT_NE(first_resolved_identity.user_id(), singleton_userid); |
| } |
| // This connects using the current client's user_id. It should be bound to the |
| // same service started above, with the same service manager-generated user |
| // id. |
| connector()->StartService(kTestSingletonAppName); |
| { |
| base::RunLoop loop; |
| Connector::TestApi test_api(connector()); |
| Identity resolved_identity; |
| test_api.SetStartServiceCallback( |
| base::Bind(&StartServiceResponse, &loop, nullptr, &resolved_identity)); |
| loop.Run(); |
| EXPECT_EQ(resolved_identity.user_id(), first_resolved_identity.user_id()); |
| } |
| } |
| |
| } // namespace service_manager |