chromium/components/policy/test_support/fake_dmserver.h

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_POLICY_TEST_SUPPORT_FAKE_DMSERVER_H_
#define COMPONENTS_POLICY_TEST_SUPPORT_FAKE_DMSERVER_H_

#include <memory>
#include <optional>
#include <set>
#include <string>

#include "base/command_line.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/values.h"
#include "chromecast/cast_core/grpc/grpc_server.h"
#include "chromecast/cast_core/grpc/grpc_unary_handler.h"
#include "components/policy/test_support/client_storage.h"
#include "components/policy/test_support/embedded_policy_test_server.h"
#include "components/policy/test_support/remote_commands_service.castcore.pb.h"
#include "components/policy/test_support/remote_commands_state.h"

/*
A bare-bones test server for testing cloud policy support.

This implements a simple cloud policy test server that can be used to test
Chrome's device management service client. The policy information is read from
the file named policy.json in the server's data directory. It contains
policies for the device and user scope, and a list of managed users. The format
of the file is JSON.
The root dictionary contains a list under the key
"managed_users". It contains auth tokens for which the server will claim that
the user is managed. The token string "*" indicates that all users are claimed
to be managed.
The root dictionary also contains a list under the key "policies". It contains
all the policies to be set, each policy has 3 fields, "policy_type" is the type
or scope of the policy (user, device or publicaccount), "entity_id" is the
account id used for public account policies, "value" is the seralized proto
message of the policies value encoded in base64.
The root dictionary also contains a "policy_user" key which indicates the
current user.
All the fields are described in the device_management_backend.proto
(https://source.chromium.org/chromium/chromium/src/+/main:components/policy/proto/device_management_backend.proto;l=516?q=PolicyData)

Example:
{
  "policies" : [
    {
      "policy_type" : "google/chromeos/user",
      "value" : "base64 encoded proto message",
    },
    {
      "policy_type" : "google/chromeos/device",
      "value" : "base64 encoded proto message",
    },
    {
      "policy_type" : "google/chromeos/publicaccount",
      "entity_id" : "[email protected]",
      "value" : "base64 encoded proto message",
    }
  ],
  "external_policies" : [
    {
      "policy_type" : "google/chrome/extension",
      "entity_id" : "extension_id",
      "value" : "base64 encoded raw json value",
    }
  ],
  "managed_users" : [
    "secret123456"
  ],
  "policy_user" : "[email protected]",
  "current_key_index": 0,
  "robot_api_auth_code": "code",
  "directory_api_id": "id",
  "request_errors": {
    "register": 500,
  }
  "device_affiliation_ids" : [
    "device_id"
  ],
  "user_affiliation_ids" : [
    "user_id"
  ],
  "allow_set_device_attributes" : false,
  "initial_enrollment_state": {
    "TEST_serial": {
      "initial_enrollment_mode": 2,
      "management_domain": "test-domain.com"
    }
  }
}
*/

namespace fakedms {

void InitLogging(const std::optional<std::string>& log_path,
                 bool log_to_console,
                 int min_log_level);
void ParseFlags(const base::CommandLine& command_line,
                std::string& policy_blob_path,
                std::string& client_state_path,
                std::string& grpc_unix_socket_path,
                std::optional<std::string>& log_path,
                base::ScopedFD& startup_pipe,
                bool& log_to_console,
                int& min_log_level);

// TODO(b/293451778): Move this to its own file.
class RemoteCommandsWaitOperation;

class FakeDMServer : public policy::EmbeddedPolicyTestServer {
 public:
  FakeDMServer(const std::string& policy_blob_path,
               const std::string& client_state_path,
               const std::string& grpc_unix_socket_path,
               base::OnceClosure shutdown_cb = base::DoNothing());
  ~FakeDMServer() override;

  // Starts the EmbeddedPolicyTestServer on a different thread other than the
  // fake_dmserver_main thread, note that the EmbeddedPolicyTestServer will
  // shut down on its own when the destructor is called.
  // Starts the gRPC server on the same thread as the fake_dmserver_main thread.
  // Returns true if it's starts the servers successfully and false otherwise.
  bool StartFakeServer();

  // Writes the host and port of the EmbeddedPolicyTestServer to the given pipe
  // in a json format {"host": "localhost", "port": 1234}, it will return true
  // if it's able to write the URL to the pipe, and false otherwise.
  bool WriteURLToPipe(base::ScopedFD&& startup_pipe);

  // Overrides the EmbeddedPolicyTestServer request handler.
  std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
      const net::test_server::HttpRequest& request) override;

 private:
  // Sets the policy payload in the policy storage, it will return true if it's
  // able to set the policy and false otherwise.
  bool SetPolicyPayload(const std::string* policy_type,
                        const std::string* entity_id,
                        const std::string* serialized_proto);
  // Sets the external policy payload in the policy storage, it will return true
  // if it's able to set the policy and false otherwise.
  bool SetExternalPolicyPayload(const std::string* policy_type,
                                const std::string* entity_id,
                                const std::string* serialized_raw_policy);

  // Parses regular and external policy from the JSON dict.
  bool ParsePolicies(const base::Value::Dict* dict);

  // Reads and sets the values in the policy blob file, it will return true if
  // the policy blob file doesn't exist yet or all the values are read
  // correctly, and false otherwise.
  bool ReadPolicyBlobFile();

  // Writes all the clients to the client state file, it will return true if
  // it's able to write the client storage to the state file, and false
  // otherwise.
  bool WriteClientStateFile();
  // Reads the client state file and registers the clients, it will return true
  // if the state file doesn't exist yet or all the values are read
  // correctly, and false otherwise.
  bool ReadClientStateFile();

  // Returns true if the key of the specific type is in the dictionary.
  static bool FindKey(const base::Value::Dict& dict,
                      const std::string& key,
                      base::Value::Type type);

  // Converts the client to Dictionary.
  static base::Value::Dict GetValueFromClient(
      const policy::ClientStorage::ClientInfo& c);
  // Converts the value to Client.
  static std::optional<policy::ClientStorage::ClientInfo> GetClientFromValue(
      const base::Value& v);

  // Starts the gRPC server on the same thread as the fake_dmserver_main thread.
  void StartGrpcServer();

  // Shuts down the the gRPC server.
  void ShutdownGrpcServer(base::OnceClosure);

  // Resets the gRPC server and shuts down the fake_dmserver_main.
  void OnShutdownGrpcServerDone(base::OnceClosure);

  // Triggers Shutting down the fake_dmserver_main and the grpc server.
  void TriggerShutdown();

  // Writes the remote command result to the reactor, it will be triggered
  // from RemoteCommandsWaitOperation::OnRemoteCommandResultAvailable.
  void OnWaitRemoteCommandResultDone(
      remote_commands::RemoteCommandsServiceHandler::WaitRemoteCommandResult::
          Reactor*,
      int64_t,
      RemoteCommandsWaitOperation*,
      bool);
  // Writes the ack to the reactor, it will be triggered
  // from RemoteCommandsWaitOperation::OnRemoteCommandAcked.
  void OnWaitRemoteCommandAckDone(
      remote_commands::RemoteCommandsServiceHandler::WaitRemoteCommandAcked::
          Reactor*,
      int64_t,
      RemoteCommandsWaitOperation*,
      bool);

  // Handles the RemoteCommandsService gRPC request SendRemoteCommand.
  void HandleSendRemoteCommand(
      remote_commands::SendRemoteCommandRequest request,
      remote_commands::RemoteCommandsServiceHandler::SendRemoteCommand::Reactor*
          reactor);

  // Handles the RemoteCommandsService gRPC request WaitRemoteCommandResult. If
  // the result isn't available withing 10 seconds, the grpc call will be
  // cancelled.
  void HandleWaitRemoteCommandResult(
      remote_commands::WaitRemoteCommandResultRequest request,
      remote_commands::RemoteCommandsServiceHandler::WaitRemoteCommandResult::
          Reactor* reactor);
  // Handles the RemoteCommandsService gRPC request WaitRemoteCommandAcked. If
  // the result isn't available withing 10 seconds, the grpc call will be
  // cancelled.
  void HandleWaitRemoteCommandAcked(
      remote_commands::WaitRemoteCommandAckedRequest request,
      remote_commands::RemoteCommandsServiceHandler::WaitRemoteCommandAcked::
          Reactor* reactor);

  // Erase the wait operation from the waiters_ set.
  void EraseWaitOperation(RemoteCommandsWaitOperation*);

  const base::FilePath policy_blob_path_;
  const base::FilePath client_state_path_;
  std::string grpc_unix_socket_uri_;

  // Sequence checker for fake_dmserver main IO thread.
  SEQUENCE_CHECKER(fake_dmserver_main_sequence_checker_);
  // Sequence checker for embedded server's request handling thread, and all the
  // functions called by HandleRequest
  SEQUENCE_CHECKER(embedded_server_sequence_checker_);

  std::optional<cast::utils::GrpcServer> grpc_server_;
  // Callback to shut down the grpc server.
  base::OnceClosure shut_down_on_main_task_runner_;
  // Callback to reset the grpc server then shut down the fake_dmserver main.
  base::OnceClosure shut_down_server_;

  // Stores the wait operations to be erased later when the remote command
  // result is available.
  std::set<std::unique_ptr<RemoteCommandsWaitOperation>,
           base::UniquePtrComparator>
      waiters_;

  base::WeakPtrFactory<FakeDMServer> weak_ptr_factory_{this};
};

}  // namespace fakedms

#endif  // COMPONENTS_POLICY_TEST_SUPPORT_FAKE_DMSERVER_H_