| // Copyright 2016 The Chromium Authors |
| // 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 <vector> |
| |
| #include "base/bind.h" |
| #include "base/guid.h" |
| #include "base/no_destructor.h" |
| #include "base/process/process.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/test/bind.h" |
| #include "base/test/gtest_util.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_suite.h" |
| #include "base/token.h" |
| #include "build/build_config.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/receiver_set.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "services/service_manager/public/cpp/binder_registry.h" |
| #include "services/service_manager/public/cpp/constants.h" |
| #include "services/service_manager/public/cpp/manifest.h" |
| #include "services/service_manager/public/cpp/manifest_builder.h" |
| #include "services/service_manager/public/cpp/service.h" |
| #include "services/service_manager/public/cpp/service_receiver.h" |
| #include "services/service_manager/public/cpp/test/test_service_manager.h" |
| #include "services/service_manager/public/mojom/service_manager.mojom.h" |
| #include "services/service_manager/tests/connect/connect.test-mojom.h" |
| #include "services/service_manager/tests/util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| // Tests that multiple services can be packaged in a single service by |
| // specifying the packaged service manifests within a parent manifest and |
| // implementing Service::CreatePackagedServiceInstance in the parent service. |
| |
| namespace service_manager { |
| |
| namespace { |
| |
| const char kTestServiceName[] = "connect_unittests"; |
| const char kTestPackageName[] = "connect_test_package"; |
| const char kTestAppName[] = "connect_test_app"; |
| const char kTestExeName[] = "connect_test_exe"; |
| const char kTestAppAName[] = "connect_test_a"; |
| const char kTestAppBName[] = "connect_test_b"; |
| const char kTestNonexistentAppName[] = "connect_test_nonexistent_app"; |
| const char kTestSandboxedAppName[] = "connect_test_sandboxed_app"; |
| const char kTestClassAppName[] = "connect_test_class_app"; |
| const char kTestSingletonAppName[] = "connect_test_singleton_app"; |
| |
| const char kIdentityTestCapability[] = "identity_test"; |
| const char kConnectTestServiceCapability[] = "connect_test_service"; |
| const char kStandaloneAppControlCapability[] = "standalone_app_control"; |
| |
| const char kConnectClassCapability[] = "connect_class"; |
| const char kExposedInterfaceCapability[] = "exposed_interface"; |
| |
| const std::vector<Manifest>& GetTestManifests() { |
| static base::NoDestructor<std::vector<Manifest>> manifests{ |
| {ManifestBuilder() |
| .WithServiceName(kTestAppName) |
| .WithOptions(ManifestOptionsBuilder() |
| .CanConnectToInstancesInAnyGroup(true) |
| .WithExecutionMode( |
| Manifest::ExecutionMode::kStandaloneExecutable) |
| .WithSandboxType("none") |
| .Build()) |
| .ExposeCapability( |
| kIdentityTestCapability, |
| Manifest::InterfaceList<test::mojom::IdentityTest>()) |
| .ExposeCapability( |
| kConnectTestServiceCapability, |
| Manifest::InterfaceList<test::mojom::ConnectTestService>()) |
| .ExposeCapability( |
| kStandaloneAppControlCapability, |
| Manifest::InterfaceList<test::mojom::StandaloneApp>()) |
| .RequireCapability(kTestClassAppName, kConnectClassCapability) |
| .RequireCapability(kTestClassAppName, kConnectTestServiceCapability) |
| .RequireCapability(kTestServiceName, kExposedInterfaceCapability) |
| .RequireCapability(kTestAppAName, kConnectTestServiceCapability) |
| .Build(), |
| ManifestBuilder() |
| .WithServiceName(kTestClassAppName) |
| .WithOptions(ManifestOptionsBuilder() |
| .WithExecutionMode( |
| Manifest::ExecutionMode::kStandaloneExecutable) |
| .WithSandboxType("none") |
| .Build()) |
| .ExposeCapability( |
| kConnectClassCapability, |
| Manifest::InterfaceList<test::mojom::ClassInterface>()) |
| .ExposeCapability( |
| kConnectTestServiceCapability, |
| Manifest::InterfaceList<test::mojom::ConnectTestService>()) |
| .Build(), |
| ManifestBuilder() |
| .WithServiceName(kTestExeName) |
| .WithOptions(ManifestOptionsBuilder() |
| .WithExecutionMode( |
| Manifest::ExecutionMode::kStandaloneExecutable) |
| .WithSandboxType("none") |
| .Build()) |
| .Build(), |
| ManifestBuilder() |
| .WithServiceName(kTestPackageName) |
| .WithOptions(ManifestOptionsBuilder() |
| .WithExecutionMode( |
| Manifest::ExecutionMode::kStandaloneExecutable) |
| .WithSandboxType("none") |
| .Build()) |
| .ExposeCapability( |
| kConnectTestServiceCapability, |
| Manifest::InterfaceList<test::mojom::ConnectTestService>()) |
| .PackageService( |
| ManifestBuilder() |
| .WithServiceName(kTestAppAName) |
| .ExposeCapability( |
| kIdentityTestCapability, |
| Manifest::InterfaceList<test::mojom::IdentityTest>()) |
| .ExposeCapability(kConnectTestServiceCapability, |
| Manifest::InterfaceList< |
| test::mojom::ConnectTestService>()) |
| .ExposeCapability( |
| kStandaloneAppControlCapability, |
| Manifest::InterfaceList<test::mojom::StandaloneApp>()) |
| .RequireCapability(kTestClassAppName, |
| kConnectClassCapability) |
| .RequireCapability(kTestServiceName, |
| kExposedInterfaceCapability) |
| .Build()) |
| .PackageService( |
| ManifestBuilder().WithServiceName(kTestAppBName).Build()) |
| .PackageService(ManifestBuilder() |
| .WithServiceName(kTestSandboxedAppName) |
| .WithOptions(ManifestOptionsBuilder() |
| .WithSandboxType("superduper") |
| .Build()) |
| .Build()) |
| .Build(), |
| ManifestBuilder() |
| .WithServiceName(kTestSingletonAppName) |
| .WithOptions(ManifestOptionsBuilder() |
| .WithInstanceSharingPolicy( |
| service_manager::Manifest:: |
| InstanceSharingPolicy::kSharedAcrossGroups) |
| .WithExecutionMode( |
| Manifest::ExecutionMode::kStandaloneExecutable) |
| .WithSandboxType("none") |
| .Build()) |
| .Build(), |
| ManifestBuilder() |
| .WithServiceName(kTestServiceName) |
| .WithOptions(ManifestOptionsBuilder() |
| .CanConnectToInstancesInAnyGroup(true) |
| .CanConnectToInstancesWithAnyId(true) |
| .Build()) |
| .ExposeCapability( |
| kExposedInterfaceCapability, |
| Manifest::InterfaceList<test::mojom::ExposedInterface>()) |
| .RequireCapability(kTestSingletonAppName, "") |
| .RequireCapability(kTestAppName, kConnectTestServiceCapability) |
| .RequireCapability(kTestAppName, kStandaloneAppControlCapability) |
| .RequireCapability(kTestAppName, kIdentityTestCapability) |
| .RequireCapability(kTestAppAName, kConnectTestServiceCapability) |
| .RequireCapability(kTestAppAName, kStandaloneAppControlCapability) |
| .RequireCapability(kTestAppAName, kIdentityTestCapability) |
| .RequireCapability(kTestPackageName, kConnectTestServiceCapability) |
| .WithInterfacesBindableOnAnyService( |
| Manifest::InterfaceList<test::mojom::AlwaysAllowedInterface>()) |
| |
| .Build()}}; |
| |
| return *manifests; |
| } |
| |
| 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 ReceiveQueryResult(mojom::ServiceInfoPtr* out_info, |
| base::RunLoop* loop, |
| mojom::ServiceInfoPtr info) { |
| *out_info = std::move(info); |
| loop->Quit(); |
| } |
| |
| void ReceiveConnectionResult(mojom::ConnectResult* out_result, |
| absl::optional<Identity>* out_target, |
| base::RunLoop* loop, |
| int32_t in_result, |
| const absl::optional<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, |
| absl::optional<Identity>* out_resolved_identity, |
| mojom::ConnectResult result, |
| const absl::optional<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(); |
| } |
| |
| class TestTargetService : public Service { |
| public: |
| explicit TestTargetService(mojo::PendingReceiver<mojom::Service> receiver) |
| : receiver_(this, std::move(receiver)) {} |
| |
| TestTargetService(const TestTargetService&) = delete; |
| TestTargetService& operator=(const TestTargetService&) = delete; |
| |
| ~TestTargetService() override = default; |
| |
| const Identity& identity() const { return receiver_.identity(); } |
| Connector* connector() { return receiver_.GetConnector(); } |
| |
| void CallOnNextBindInterface(base::OnceClosure callback) { |
| next_bind_interface_callback_ = std::move(callback); |
| } |
| |
| void WaitForStart() { wait_for_start_loop_.Run(); } |
| |
| void WaitForBindInterface() { |
| wait_for_bind_interface_loop_.emplace(); |
| wait_for_bind_interface_loop_->Run(); |
| } |
| |
| void QuitGracefullyAndWait() { |
| receiver_.RequestClose(); |
| wait_for_disconnect_loop_.Run(); |
| } |
| |
| private: |
| // Service: |
| void OnStart() override { wait_for_start_loop_.Quit(); } |
| void OnBindInterface(const BindSourceInfo& source, |
| const std::string& interface_name, |
| mojo::ScopedMessagePipeHandle interface_pipe) override { |
| if (next_bind_interface_callback_) |
| std::move(next_bind_interface_callback_).Run(); |
| if (wait_for_bind_interface_loop_) |
| wait_for_bind_interface_loop_->Quit(); |
| } |
| void OnDisconnected() override { wait_for_disconnect_loop_.Quit(); } |
| |
| ServiceReceiver receiver_; |
| base::RunLoop wait_for_start_loop_; |
| base::RunLoop wait_for_disconnect_loop_; |
| absl::optional<base::RunLoop> wait_for_bind_interface_loop_; |
| base::OnceClosure next_bind_interface_callback_; |
| }; |
| |
| class ConnectTest : public testing::Test, |
| public Service, |
| public test::mojom::ExposedInterface { |
| public: |
| ConnectTest() : test_service_manager_(GetTestManifests()) {} |
| |
| ConnectTest(const ConnectTest&) = delete; |
| ConnectTest& operator=(const ConnectTest&) = delete; |
| |
| ~ConnectTest() override = default; |
| |
| Connector* connector() { return service_receiver_.GetConnector(); } |
| |
| protected: |
| void CompareConnectionState( |
| const std::string& connection_local_name, |
| const std::string& connection_remote_name, |
| const absl::optional<base::Token>& connection_remote_instance_group, |
| const std::string& initialize_local_name, |
| const absl::optional<base::Token>& initialize_local_instance_group) { |
| EXPECT_EQ(connection_remote_name, |
| connection_state_->connection_remote_name); |
| EXPECT_EQ(connection_remote_instance_group, |
| connection_state_->connection_remote_instance_group); |
| EXPECT_EQ(initialize_local_name, connection_state_->initialize_local_name); |
| EXPECT_EQ(initialize_local_instance_group, |
| connection_state_->initialize_local_instance_group); |
| } |
| |
| mojo::PendingReceiver<mojom::Service> RegisterServiceInstance( |
| const std::string& service_name) { |
| return test_service_manager_.RegisterInstance( |
| Identity{service_name, service_receiver_.identity().instance_group(), |
| base::Token{}, base::Token::CreateRandom()}); |
| } |
| |
| private: |
| // testing::Test: |
| void SetUp() override { |
| service_receiver_.Bind( |
| test_service_manager_.RegisterTestInstance(kTestServiceName)); |
| |
| mojo::Remote<test::mojom::ConnectTestService> root_service; |
| connector()->Connect(kTestPackageName, |
| root_service.BindNewPipeAndPassReceiver()); |
| |
| base::RunLoop run_loop; |
| std::string root_name; |
| root_service->GetTitle( |
| base::BindOnce(&ReceiveOneString, &root_name, &run_loop)); |
| run_loop.Run(); |
| } |
| |
| // Service: |
| void OnBindInterface(const BindSourceInfo& source, |
| const std::string& interface_name, |
| mojo::ScopedMessagePipeHandle interface_pipe) override { |
| CHECK_EQ(test::mojom::ExposedInterface::Name_, interface_name); |
| receivers_.Add(this, mojo::PendingReceiver<test::mojom::ExposedInterface>( |
| std::move(interface_pipe))); |
| } |
| |
| // test::mojom::ExposedInterface: |
| void ConnectionAccepted(test::mojom::ConnectionStatePtr state) override { |
| connection_state_ = std::move(state); |
| } |
| |
| base::test::TaskEnvironment task_environment_; |
| TestServiceManager test_service_manager_; |
| ServiceReceiver service_receiver_{this}; |
| mojo::ReceiverSet<test::mojom::ExposedInterface> receivers_; |
| test::mojom::ConnectionStatePtr connection_state_; |
| }; |
| |
| // Ensure the connection was properly established and that a round trip |
| // method call/response is completed. |
| TEST_F(ConnectTest, BindInterface) { |
| mojo::Remote<test::mojom::ConnectTestService> service; |
| connector()->Connect(kTestAppName, service.BindNewPipeAndPassReceiver()); |
| base::RunLoop run_loop; |
| std::string title; |
| service->GetTitle(base::BindOnce(&ReceiveOneString, &title, &run_loop)); |
| run_loop.Run(); |
| EXPECT_EQ("APP", title); |
| } |
| |
| TEST_F(ConnectTest, Instances) { |
| const base::Token kInstanceIdA{1, 2}; |
| const base::Token kInstanceIdB{3, 4}; |
| auto filter_a = ServiceFilter::ByNameWithId(kTestAppName, kInstanceIdA); |
| base::Token instance_a1, instance_a2; |
| mojo::Remote<test::mojom::ConnectTestService> service_a1; |
| { |
| connector()->Connect(filter_a, service_a1.BindNewPipeAndPassReceiver()); |
| base::RunLoop loop; |
| service_a1->GetInstanceId( |
| base::BindLambdaForTesting([&](const base::Token& instance_id) { |
| instance_a1 = instance_id; |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| mojo::Remote<test::mojom::ConnectTestService> service_a2; |
| { |
| connector()->Connect(filter_a, service_a2.BindNewPipeAndPassReceiver()); |
| base::RunLoop loop; |
| service_a2->GetInstanceId( |
| base::BindLambdaForTesting([&](const base::Token& instance_id) { |
| instance_a2 = instance_id; |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| EXPECT_EQ(instance_a1, instance_a2); |
| |
| auto filter_b = ServiceFilter::ByNameWithId(kTestAppName, kInstanceIdB); |
| base::Token instance_b; |
| mojo::Remote<test::mojom::ConnectTestService> service_b; |
| { |
| connector()->Connect(filter_b, service_b.BindNewPipeAndPassReceiver()); |
| base::RunLoop loop; |
| service_b->GetInstanceId( |
| base::BindLambdaForTesting([&](const base::Token& instance_id) { |
| instance_b = instance_id; |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| EXPECT_NE(instance_a1, instance_b); |
| } |
| |
| TEST_F(ConnectTest, ConnectWithGloballyUniqueId) { |
| absl::optional<TestTargetService> target( |
| absl::in_place, RegisterServiceInstance(kTestAppAName)); |
| target->WaitForStart(); |
| |
| Identity specific_identity = target->identity(); |
| EXPECT_TRUE(specific_identity.IsValid()); |
| |
| // First connect with a basic identity. |
| mojo::Remote<test::mojom::ConnectTestService> proxy; |
| connector()->Connect(kTestAppAName, proxy.BindNewPipeAndPassReceiver()); |
| target->WaitForBindInterface(); |
| |
| // Now connect with a very specific identity, including globally unique ID. |
| proxy.reset(); |
| connector()->Connect(specific_identity, proxy.BindNewPipeAndPassReceiver()); |
| target->WaitForBindInterface(); |
| |
| // Now quit the test service and start a new instance. |
| target->QuitGracefullyAndWait(); |
| |
| target.emplace(RegisterServiceInstance(kTestAppAName)); |
| target->WaitForStart(); |
| |
| Identity new_specific_identity = target->identity(); |
| |
| // This must differ from the old identity because all instances should have |
| // a globally unique ID. |
| EXPECT_NE(specific_identity, new_specific_identity); |
| |
| // Connect to the new instance with a basic identity, and with its specific |
| // identity. Both should succeed. |
| proxy.reset(); |
| connector()->Connect(kTestAppAName, proxy.BindNewPipeAndPassReceiver()); |
| target->WaitForBindInterface(); |
| proxy.reset(); |
| connector()->Connect(new_specific_identity, |
| proxy.BindNewPipeAndPassReceiver()); |
| target->WaitForBindInterface(); |
| |
| // Now attempt to connect using the specific identity of the previous |
| // instance. This request should not be seen by the new instance, and |proxy| |
| // should be disconnected when the Service Manager drops the request. |
| base::RunLoop wait_for_error_loop; |
| base::RunLoop wait_for_connect_loop; |
| proxy.reset(); |
| target->CallOnNextBindInterface(base::BindOnce([] { NOTREACHED(); })); |
| connector()->Connect( |
| specific_identity, proxy.BindNewPipeAndPassReceiver(), |
| base::BindLambdaForTesting([&](mojom::ConnectResult result, |
| const absl::optional<Identity>& identity) { |
| EXPECT_EQ(mojom::ConnectResult::ACCESS_DENIED, result); |
| wait_for_connect_loop.Quit(); |
| })); |
| proxy.set_disconnect_handler(wait_for_error_loop.QuitClosure()); |
| wait_for_connect_loop.Run(); |
| wait_for_error_loop.Run(); |
| } |
| |
| TEST_F(ConnectTest, QueryService) { |
| mojom::ServiceInfoPtr service_info; |
| base::RunLoop run_loop; |
| connector()->QueryService( |
| kTestSandboxedAppName, |
| base::BindOnce(&ReceiveQueryResult, &service_info, &run_loop)); |
| run_loop.Run(); |
| ASSERT_TRUE(service_info); |
| EXPECT_EQ("superduper", service_info->sandbox_type); |
| } |
| |
| TEST_F(ConnectTest, QueryNonexistentService) { |
| mojom::ServiceInfoPtr service_info; |
| base::RunLoop run_loop; |
| connector()->QueryService( |
| kTestNonexistentAppName, |
| base::BindOnce(&ReceiveQueryResult, &service_info, &run_loop)); |
| run_loop.Run(); |
| EXPECT_FALSE(service_info); |
| } |
| |
| #if DCHECK_IS_ON() |
| // This test triggers intentional DCHECKs but is not suitable for death testing. |
| #define MAYBE_BlockedInterface DISABLED_BlockedInterface |
| #else |
| #define MAYBE_BlockedInterface BlockedInterface |
| #endif |
| |
| // BlockedInterface should not be exposed to this application because it is not |
| // in our CapabilityFilter whitelist. |
| TEST_F(ConnectTest, MAYBE_BlockedInterface) { |
| base::RunLoop run_loop; |
| mojo::Remote<test::mojom::BlockedInterface> blocked; |
| connector()->Connect(kTestAppName, blocked.BindNewPipeAndPassReceiver()); |
| blocked.set_disconnect_handler(base::BindOnce(&QuitLoop, &run_loop)); |
| std::string title = "unchanged"; |
| blocked->GetTitleBlocked( |
| base::BindOnce(&ReceiveOneString, &title, &run_loop)); |
| run_loop.Run(); |
| EXPECT_EQ("unchanged", title); |
| } |
| |
| TEST_F(ConnectTest, AlwaysAllowedInterface) { |
| base::RunLoop run_loop; |
| mojo::Remote<test::mojom::AlwaysAllowedInterface> always_allowed; |
| connector()->Connect(ServiceFilter::ByName(kTestAppAName), |
| always_allowed.BindNewPipeAndPassReceiver()); |
| always_allowed.set_disconnect_handler(base::BindOnce(&QuitLoop, &run_loop)); |
| std::string title = "unchanged"; |
| always_allowed->GetTitleAlwaysAllowed( |
| base::BindOnce(&ReceiveOneString, &title, &run_loop)); |
| run_loop.Run(); |
| EXPECT_EQ("always_allowed", title); |
| } |
| |
| // Connects to an app provided by a package. |
| TEST_F(ConnectTest, PackagedApp) { |
| absl::optional<Identity> resolved_identity; |
| base::RunLoop run_loop; |
| mojo::Remote<test::mojom::ConnectTestService> service_a; |
| connector()->Connect(ServiceFilter::ByName(kTestAppAName), |
| service_a.BindNewPipeAndPassReceiver(), |
| base::BindOnce(&StartServiceResponse, nullptr, nullptr, |
| &resolved_identity)); |
| std::string a_name; |
| service_a->GetTitle(base::BindOnce(&ReceiveOneString, &a_name, &run_loop)); |
| run_loop.Run(); |
| EXPECT_EQ("A", a_name); |
| ASSERT_TRUE(resolved_identity); |
| EXPECT_EQ(resolved_identity->name(), kTestAppAName); |
| } |
| |
| #if DCHECK_IS_ON() |
| // This test triggers intentional DCHECKs but is not suitable for death testing. |
| #define MAYBE_BlockedPackage DISABLED_BlockedPackage |
| #else |
| #define MAYBE_BlockedPackage BlockedPackage |
| #endif |
| |
| // 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, MAYBE_BlockedPackage) { |
| mojo::Remote<test::mojom::StandaloneApp> standalone_app; |
| connector()->Connect(kTestAppName, |
| standalone_app.BindNewPipeAndPassReceiver()); |
| base::RunLoop run_loop; |
| std::string title; |
| standalone_app->ConnectToAllowedAppInBlockedPackage( |
| base::BindOnce(&ReceiveOneString, &title, &run_loop)); |
| run_loop.Run(); |
| EXPECT_EQ("A", title); |
| } |
| |
| #if DCHECK_IS_ON() |
| // This test triggers intentional DCHECKs but is not suitable for death testing. |
| #define MAYBE_PackagedApp_BlockedInterface DISABLED_PackagedApp_BlockedInterface |
| #else |
| #define MAYBE_PackagedApp_BlockedInterface PackagedApp_BlockedInterface |
| #endif |
| |
| // BlockedInterface should not be exposed to this application because it is not |
| // in our CapabilityFilter whitelist. |
| TEST_F(ConnectTest, MAYBE_PackagedApp_BlockedInterface) { |
| base::RunLoop run_loop; |
| mojo::Remote<test::mojom::BlockedInterface> blocked; |
| connector()->Connect(kTestAppAName, blocked.BindNewPipeAndPassReceiver()); |
| blocked.set_disconnect_handler(base::BindOnce(&QuitLoop, &run_loop)); |
| run_loop.Run(); |
| } |
| |
| #if DCHECK_IS_ON() |
| // This test triggers intentional DCHECKs but is not suitable for death testing. |
| #define MAYBE_BlockedPackagedApplication DISABLED_BlockedPackagedApplication |
| #else |
| #define MAYBE_BlockedPackagedApplication BlockedPackagedApplication |
| #endif |
| |
| // Connection to another application provided by the same package, blocked |
| // because it's not in the capability filter whitelist. |
| TEST_F(ConnectTest, MAYBE_BlockedPackagedApplication) { |
| base::RunLoop run_loop; |
| mojo::Remote<test::mojom::ConnectTestService> service_b; |
| connector()->Connect( |
| ServiceFilter::ByName(kTestAppBName), |
| service_b.BindNewPipeAndPassReceiver(), |
| base::BindLambdaForTesting([&](mojom::ConnectResult result, |
| const absl::optional<Identity>& identity) { |
| EXPECT_EQ(mojom::ConnectResult::ACCESS_DENIED, result); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| TEST_F(ConnectTest, CapabilityClasses) { |
| mojo::Remote<test::mojom::StandaloneApp> standalone_app; |
| connector()->Connect(kTestAppName, |
| standalone_app.BindNewPipeAndPassReceiver()); |
| std::string string1, string2; |
| base::RunLoop loop; |
| standalone_app->ConnectToClassInterface( |
| base::BindOnce(&ReceiveTwoStrings, &string1, &string2, &loop)); |
| loop.Run(); |
| EXPECT_EQ("PONG", string1); |
| EXPECT_EQ("CLASS APP", string2); |
| } |
| |
| #if DCHECK_IS_ON() |
| // This test triggers intentional DCHECKs but is not suitable for death testing. |
| #define MAYBE_ConnectWithoutExplicitClassBlocked \ |
| DISABLED_ConnectWithoutExplicitClassBlocked |
| #else |
| #define MAYBE_ConnectWithoutExplicitClassBlocked \ |
| ConnectWithoutExplicitClassBlocked |
| #endif |
| |
| TEST_F(ConnectTest, MAYBE_ConnectWithoutExplicitClassBlocked) { |
| // We not be able to bind a ClassInterface remote 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. |
| mojo::Remote<test::mojom::ClassInterface> class_interface; |
| connector()->Connect(kTestClassAppName, |
| class_interface.BindNewPipeAndPassReceiver()); |
| base::RunLoop loop; |
| class_interface.set_disconnect_handler(base::BindOnce(&QuitLoop, &loop)); |
| loop.Run(); |
| } |
| |
| TEST_F(ConnectTest, ConnectToDifferentGroup_Allowed) { |
| mojo::Remote<test::mojom::IdentityTest> identity_test; |
| connector()->Connect(ServiceFilter::ByName(kTestAppName), |
| identity_test.BindNewPipeAndPassReceiver()); |
| mojom::ConnectResult result; |
| auto filter = ServiceFilter::ByNameInGroup(kTestClassAppName, |
| base::Token::CreateRandom()); |
| absl::optional<Identity> result_identity; |
| { |
| base::RunLoop loop; |
| identity_test->ConnectToClassAppWithFilter( |
| filter, base::BindOnce(&ReceiveConnectionResult, &result, |
| &result_identity, &loop)); |
| loop.Run(); |
| } |
| EXPECT_EQ(result, mojom::ConnectResult::SUCCEEDED); |
| ASSERT_TRUE(result_identity); |
| EXPECT_EQ(filter.service_name(), result_identity->name()); |
| EXPECT_EQ(*filter.instance_group(), result_identity->instance_group()); |
| EXPECT_TRUE(result_identity->instance_id().is_zero()); |
| EXPECT_FALSE(result_identity->globally_unique_id().is_zero()); |
| } |
| |
| TEST_F(ConnectTest, ConnectToDifferentGroup_Blocked) { |
| mojo::Remote<test::mojom::IdentityTest> identity_test; |
| connector()->Connect(ServiceFilter::ByName(kTestAppAName), |
| identity_test.BindNewPipeAndPassReceiver()); |
| mojom::ConnectResult result; |
| auto filter = ServiceFilter::ByNameInGroup(kTestClassAppName, |
| base::Token::CreateRandom()); |
| absl::optional<Identity> result_identity; |
| { |
| base::RunLoop loop; |
| identity_test->ConnectToClassAppWithFilter( |
| filter, base::BindOnce(&ReceiveConnectionResult, &result, |
| &result_identity, &loop)); |
| loop.Run(); |
| } |
| EXPECT_EQ(mojom::ConnectResult::ACCESS_DENIED, result); |
| EXPECT_FALSE(result_identity); |
| } |
| |
| TEST_F(ConnectTest, ConnectWithDifferentInstanceId_Blocked) { |
| mojo::Remote<test::mojom::IdentityTest> identity_test; |
| connector()->Connect(ServiceFilter::ByName(kTestAppAName), |
| identity_test.BindNewPipeAndPassReceiver()); |
| |
| mojom::ConnectResult result; |
| auto filter = ServiceFilter::ByNameWithId(kTestClassAppName, |
| base::Token::CreateRandom()); |
| absl::optional<Identity> result_identity; |
| base::RunLoop loop; |
| identity_test->ConnectToClassAppWithFilter( |
| filter, base::BindOnce(&ReceiveConnectionResult, &result, |
| &result_identity, &loop)); |
| loop.Run(); |
| EXPECT_EQ(mojom::ConnectResult::ACCESS_DENIED, result); |
| EXPECT_FALSE(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 BUILDFLAG(IS_WIN) |
| base::StrCat({kTestExeName, ".exe"}), |
| #else |
| kTestExeName, |
| #endif |
| service_manager::Identity(kTestExeName, kSystemInstanceGroup, |
| base::Token{}, base::Token::CreateRandom()), |
| connector(), &process); |
| EXPECT_EQ(result, mojom::ConnectResult::ACCESS_DENIED); |
| } |
| |
| // Verifies that a client with the "shared_instance_across_users" value of |
| // "instance_sharing" option can receive connections from clients run as other |
| // users. |
| TEST_F(ConnectTest, AllUsersSingleton) { |
| absl::optional<Identity> first_resolved_identity; |
| { |
| base::RunLoop loop; |
| const base::Token singleton_instance_group = base::Token::CreateRandom(); |
| // Connect to an instance with an explicit different instance group. This |
| // supplied group should be ignored by the Service Manager because the |
| // target service is a singleton, and the Service Manager always generates a |
| // random instance group to host singleton service instances. |
| connector()->WarmService( |
| service_manager::ServiceFilter::ByNameInGroup(kTestSingletonAppName, |
| singleton_instance_group), |
| base::BindOnce(&StartServiceResponse, &loop, nullptr, |
| &first_resolved_identity)); |
| loop.Run(); |
| ASSERT_TRUE(first_resolved_identity); |
| EXPECT_NE(first_resolved_identity->instance_group(), |
| singleton_instance_group); |
| } |
| { |
| base::RunLoop loop; |
| absl::optional<Identity> resolved_identity; |
| // This connects using the current client's instance group. It should be |
| // get routed to the same service instance started above. |
| connector()->WarmService( |
| service_manager::ServiceFilter::ByName(kTestSingletonAppName), |
| base::BindOnce(&StartServiceResponse, &loop, nullptr, |
| &resolved_identity)); |
| loop.Run(); |
| ASSERT_TRUE(resolved_identity); |
| EXPECT_EQ(*resolved_identity, *first_resolved_identity); |
| } |
| } |
| |
| } // namespace |
| } // namespace service_manager |