1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/policy/test/local_policy_test_server.h"
6
7#include <ctype.h>
8
9#include <algorithm>
10#include <vector>
11
12#include "base/base_paths.h"
13#include "base/files/file_util.h"
14#include "base/json/json_writer.h"
15#include "base/path_service.h"
16#include "base/stl_util.h"
17#include "base/strings/stringprintf.h"
18#include "components/policy/core/common/cloud/cloud_policy_constants.h"
19#include "crypto/rsa_private_key.h"
20#include "net/test/python_utils.h"
21#include "net/test/spawned_test_server/base_test_server.h"
22
23namespace policy {
24
25namespace {
26
27// Filename in the temporary directory storing the policy data.
28const base::FilePath::CharType kPolicyFileName[] = FILE_PATH_LITERAL("policy");
29
30// Private signing key file within the temporary directory.
31const base::FilePath::CharType kSigningKeyFileName[] =
32    FILE_PATH_LITERAL("signing_key");
33
34// Private signing key signature file within the temporary directory.
35const base::FilePath::CharType kSigningKeySignatureFileName[] =
36    FILE_PATH_LITERAL("signing_key.sig");
37
38// The file containing client definitions to be passed to the server.
39const base::FilePath::CharType kClientStateFileName[] =
40    FILE_PATH_LITERAL("clients");
41
42// Dictionary keys for the client state file. Needs to be kept in sync with
43// policy_testserver.py.
44const char kClientStateKeyAllowedPolicyTypes[] = "allowed_policy_types";
45const char kClientStateKeyDeviceId[] = "device_id";
46const char kClientStateKeyDeviceToken[] = "device_token";
47const char kClientStateKeyMachineName[] = "machine_name";
48const char kClientStateKeyMachineId[] = "machine_id";
49
50// Checks whether a given character should be replaced when constructing a file
51// name. To keep things simple, this is a bit over-aggressive. Needs to be kept
52// in sync with policy_testserver.py.
53bool IsUnsafeCharacter(char c) {
54  return !(isalnum(c) || c == '.' || c == '@' || c == '-');
55}
56
57}  // namespace
58
59LocalPolicyTestServer::LocalPolicyTestServer()
60    : net::LocalTestServer(net::BaseTestServer::TYPE_HTTP,
61                           net::BaseTestServer::kLocalhost,
62                           base::FilePath()) {
63  CHECK(server_data_dir_.CreateUniqueTempDir());
64  config_file_ = server_data_dir_.path().Append(kPolicyFileName);
65}
66
67LocalPolicyTestServer::LocalPolicyTestServer(const base::FilePath& config_file)
68    : net::LocalTestServer(net::BaseTestServer::TYPE_HTTP,
69                           net::BaseTestServer::kLocalhost,
70                           base::FilePath()),
71      config_file_(config_file) {}
72
73LocalPolicyTestServer::LocalPolicyTestServer(const std::string& test_name)
74    : net::LocalTestServer(net::BaseTestServer::TYPE_HTTP,
75                           net::BaseTestServer::kLocalhost,
76                           base::FilePath()) {
77  // Read configuration from a file in chrome/test/data/policy.
78  base::FilePath source_root;
79  CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &source_root));
80  config_file_ = source_root
81      .AppendASCII("chrome")
82      .AppendASCII("test")
83      .AppendASCII("data")
84      .AppendASCII("policy")
85      .AppendASCII(base::StringPrintf("policy_%s.json", test_name.c_str()));
86}
87
88LocalPolicyTestServer::~LocalPolicyTestServer() {}
89
90bool LocalPolicyTestServer::SetSigningKeyAndSignature(
91    const crypto::RSAPrivateKey* key, const std::string& signature) {
92  CHECK(server_data_dir_.IsValid());
93
94  std::vector<uint8> signing_key_bits;
95  if (!key->ExportPrivateKey(&signing_key_bits))
96    return false;
97
98  policy_key_ = server_data_dir_.path().Append(kSigningKeyFileName);
99  int bytes_written = base::WriteFile(
100      policy_key_,
101      reinterpret_cast<const char*>(vector_as_array(&signing_key_bits)),
102      signing_key_bits.size());
103
104  if (bytes_written != static_cast<int>(signing_key_bits.size()))
105    return false;
106
107  // Write the signature data.
108  base::FilePath signature_file = server_data_dir_.path().Append(
109      kSigningKeySignatureFileName);
110  bytes_written = base::WriteFile(
111      signature_file,
112      signature.c_str(),
113      signature.size());
114
115  return bytes_written == static_cast<int>(signature.size());
116}
117
118void LocalPolicyTestServer::RegisterClient(const std::string& dm_token,
119                                           const std::string& device_id) {
120  CHECK(server_data_dir_.IsValid());
121
122  scoped_ptr<base::DictionaryValue> client_dict(new base::DictionaryValue());
123  client_dict->SetString(kClientStateKeyDeviceId, device_id);
124  client_dict->SetString(kClientStateKeyDeviceToken, dm_token);
125  client_dict->SetString(kClientStateKeyMachineName, std::string());
126  client_dict->SetString(kClientStateKeyMachineId, std::string());
127
128  // Allow all policy types for now.
129  scoped_ptr<base::ListValue> types(new base::ListValue());
130  types->AppendString(dm_protocol::kChromeDevicePolicyType);
131  types->AppendString(dm_protocol::kChromeUserPolicyType);
132  types->AppendString(dm_protocol::kChromePublicAccountPolicyType);
133  types->AppendString(dm_protocol::kChromeExtensionPolicyType);
134
135  client_dict->Set(kClientStateKeyAllowedPolicyTypes, types.release());
136  clients_.Set(dm_token, client_dict.release());
137}
138
139bool LocalPolicyTestServer::UpdatePolicy(const std::string& type,
140                                         const std::string& entity_id,
141                                         const std::string& policy) {
142  CHECK(server_data_dir_.IsValid());
143
144  std::string selector = GetSelector(type, entity_id);
145  base::FilePath policy_file = server_data_dir_.path().AppendASCII(
146      base::StringPrintf("policy_%s.bin", selector.c_str()));
147
148  return base::WriteFile(policy_file, policy.c_str(), policy.size()) ==
149      static_cast<int>(policy.size());
150}
151
152bool LocalPolicyTestServer::UpdatePolicyData(const std::string& type,
153                                             const std::string& entity_id,
154                                             const std::string& data) {
155  CHECK(server_data_dir_.IsValid());
156
157  std::string selector = GetSelector(type, entity_id);
158  base::FilePath data_file = server_data_dir_.path().AppendASCII(
159      base::StringPrintf("policy_%s.data", selector.c_str()));
160
161  return base::WriteFile(data_file, data.c_str(), data.size()) ==
162      static_cast<int>(data.size());
163}
164
165GURL LocalPolicyTestServer::GetServiceURL() const {
166  return GetURL("device_management");
167}
168
169bool LocalPolicyTestServer::SetPythonPath() const {
170  if (!net::LocalTestServer::SetPythonPath())
171    return false;
172
173  // Add the net/tools/testserver directory to the path.
174  base::FilePath net_testserver_path;
175  if (!LocalTestServer::GetTestServerPath(&net_testserver_path)) {
176    LOG(ERROR) << "Failed to get net testserver path.";
177    return false;
178  }
179  AppendToPythonPath(net_testserver_path.DirName());
180
181  // We need protobuf python bindings.
182  base::FilePath third_party_dir;
183  if (!PathService::Get(base::DIR_SOURCE_ROOT, &third_party_dir)) {
184    LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT";
185    return false;
186  }
187  AppendToPythonPath(third_party_dir
188                     .AppendASCII("third_party")
189                     .AppendASCII("protobuf")
190                     .AppendASCII("python"));
191
192  // Add the generated python protocol buffer bindings.
193  base::FilePath pyproto_dir;
194  if (!GetPyProtoPath(&pyproto_dir)) {
195    LOG(ERROR) << "Cannot find pyproto dir for generated code.";
196    return false;
197  }
198
199  AppendToPythonPath(pyproto_dir
200                     .AppendASCII("chrome")
201                     .AppendASCII("browser")
202                     .AppendASCII("policy")
203                     .AppendASCII("proto")
204                     .AppendASCII("cloud"));
205  AppendToPythonPath(pyproto_dir
206                     .AppendASCII("policy")
207                     .AppendASCII("proto"));
208#if defined(OS_CHROMEOS)
209  AppendToPythonPath(pyproto_dir
210                     .AppendASCII("chrome")
211                     .AppendASCII("browser")
212                     .AppendASCII("policy")
213                     .AppendASCII("proto")
214                     .AppendASCII("chromeos"));
215#endif
216
217  return true;
218}
219
220bool LocalPolicyTestServer::GetTestServerPath(
221    base::FilePath* testserver_path) const {
222  base::FilePath source_root;
223  if (!PathService::Get(base::DIR_SOURCE_ROOT, &source_root)) {
224    LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT";
225    return false;
226  }
227  *testserver_path = source_root
228      .AppendASCII("chrome")
229      .AppendASCII("browser")
230      .AppendASCII("policy")
231      .AppendASCII("test")
232      .AppendASCII("policy_testserver.py");
233  return true;
234}
235
236bool LocalPolicyTestServer::GenerateAdditionalArguments(
237    base::DictionaryValue* arguments) const {
238  if (!net::LocalTestServer::GenerateAdditionalArguments(arguments))
239    return false;
240
241  arguments->SetString("config-file", config_file_.AsUTF8Unsafe());
242  if (!policy_key_.empty())
243    arguments->SetString("policy-key", policy_key_.AsUTF8Unsafe());
244  if (server_data_dir_.IsValid()) {
245    arguments->SetString("data-dir", server_data_dir_.path().AsUTF8Unsafe());
246
247    if (!clients_.empty()) {
248      std::string json;
249      base::JSONWriter::Write(&clients_, &json);
250      base::FilePath client_state_file =
251          server_data_dir_.path().Append(kClientStateFileName);
252      if (base::WriteFile(client_state_file, json.c_str(), json.size()) !=
253          static_cast<int>(json.size())) {
254        return false;
255      }
256      arguments->SetString("client-state", client_state_file.AsUTF8Unsafe());
257    }
258  }
259
260  return true;
261}
262
263std::string LocalPolicyTestServer::GetSelector(const std::string& type,
264                                               const std::string& entity_id) {
265  std::string selector = type;
266  if (!entity_id.empty())
267    selector = base::StringPrintf("%s/%s", type.c_str(), entity_id.c_str());
268  std::replace_if(selector.begin(), selector.end(), IsUnsafeCharacter, '_');
269  return selector;
270}
271
272}  // namespace policy
273