blob: c3b2abf6c483ef8f68aa455fb5ebd6ef4d26e484 [file] [log] [blame]
// 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