summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormnissler@chromium.org <mnissler@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-11-11 08:47:14 +0000
committermnissler@chromium.org <mnissler@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-11-11 08:47:14 +0000
commit5ceb5e9ce90a6287436857bed52dd98bc60330a0 (patch)
tree0fd44b2d525b2d4cdaa48eda4e799b1f8bb6277d
parent3a8bdeaf675f4496a79b0adc034671cce3ffff00 (diff)
downloadchromium_src-5ceb5e9ce90a6287436857bed52dd98bc60330a0.zip
chromium_src-5ceb5e9ce90a6287436857bed52dd98bc60330a0.tar.gz
chromium_src-5ceb5e9ce90a6287436857bed52dd98bc60330a0.tar.bz2
Add device management test server to net infrastructure.
This adds a simple device management server implementation written in python that's useful for unit tests and local testing. BUG=62318 TEST=Compiles and browser tests in device_management_backend_browsertest succeed Review URL: http://codereview.chromium.org/4659002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@65783 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/policy/device_management_backend_impl_browsertest.cc55
-rw-r--r--chrome/browser/policy/proto/device_management_proto.gyp79
-rw-r--r--chrome/chrome_browser.gypi50
-rw-r--r--chrome/test/data/policy/device_management3
-rw-r--r--net/net.gyp1
-rw-r--r--net/test/test_server.cc2
-rw-r--r--net/tools/testserver/device_management.py318
-rwxr-xr-xnet/tools/testserver/testserver.py27
8 files changed, 480 insertions, 55 deletions
diff --git a/chrome/browser/policy/device_management_backend_impl_browsertest.cc b/chrome/browser/policy/device_management_backend_impl_browsertest.cc
index 43df7be..770317b 100644
--- a/chrome/browser/policy/device_management_backend_impl_browsertest.cc
+++ b/chrome/browser/policy/device_management_backend_impl_browsertest.cc
@@ -8,6 +8,7 @@
#include "chrome/browser/browser_thread.h"
#include "chrome/browser/policy/device_management_backend_mock.h"
#include "chrome/test/in_process_browser_test.h"
+#include "net/test/test_server.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_test_job.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -81,10 +82,6 @@ class DeviceManagementBackendImplIntegrationTest : public InProcessBrowserTest {
}
protected:
- DeviceManagementBackendImplIntegrationTest() {
- URLFetcher::enable_interception_for_tests(true);
- }
-
std::string token_;
};
@@ -93,7 +90,8 @@ static void QuitMessageLoop() {
}
IN_PROC_BROWSER_TEST_F(DeviceManagementBackendImplIntegrationTest,
- RegisterAndFetchPolicy) {
+ CannedResponses) {
+ URLFetcher::enable_interception_for_tests(true);
DeviceManagementBackendImpl service(kServiceUrl);
{
@@ -138,4 +136,51 @@ IN_PROC_BROWSER_TEST_F(DeviceManagementBackendImplIntegrationTest,
}
}
+IN_PROC_BROWSER_TEST_F(DeviceManagementBackendImplIntegrationTest,
+ WithTestServer) {
+ net::TestServer test_server(
+ net::TestServer::TYPE_HTTP,
+ FilePath(FILE_PATH_LITERAL("chrome/test/data/policy")));
+ ASSERT_TRUE(test_server.Start());
+ DeviceManagementBackendImpl service(
+ test_server.GetURL("device_management").spec());
+
+ {
+ DeviceRegisterResponseDelegateMock delegate;
+ EXPECT_CALL(delegate, HandleRegisterResponse(_))
+ .WillOnce(DoAll(Invoke(this, &DeviceManagementBackendImplIntegrationTest
+ ::CaptureToken),
+ InvokeWithoutArgs(QuitMessageLoop)));
+ em::DeviceRegisterRequest request;
+ service.ProcessRegisterRequest("token", "device id", request, &delegate);
+ MessageLoop::current()->Run();
+ }
+
+ {
+ em::DevicePolicyResponse expected_response;
+
+ DevicePolicyResponseDelegateMock delegate;
+ EXPECT_CALL(delegate, HandlePolicyResponse(_))
+ .WillOnce(InvokeWithoutArgs(QuitMessageLoop));
+ em::DevicePolicyRequest request;
+ request.set_policy_scope("chrome");
+ em::DevicePolicySettingRequest* setting_request =
+ request.add_setting_request();
+ setting_request->set_key("policy");
+ service.ProcessPolicyRequest(token_, request, &delegate);
+
+ MessageLoop::current()->Run();
+ }
+
+ {
+ DeviceUnregisterResponseDelegateMock delegate;
+ EXPECT_CALL(delegate, HandleUnregisterResponse(_))
+ .WillOnce(InvokeWithoutArgs(QuitMessageLoop));
+ em::DeviceUnregisterRequest request;
+ service.ProcessUnregisterRequest(token_, request, &delegate);
+
+ MessageLoop::current()->Run();
+ }
+}
+
} // namespace policy
diff --git a/chrome/browser/policy/proto/device_management_proto.gyp b/chrome/browser/policy/proto/device_management_proto.gyp
new file mode 100644
index 0000000..c8b61e0
--- /dev/null
+++ b/chrome/browser/policy/proto/device_management_proto.gyp
@@ -0,0 +1,79 @@
+# Copyright (c) 2010 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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ 'protoc_out_dir': '<(SHARED_INTERMEDIATE_DIR)/protoc_out',
+ },
+ 'targets': [
+ {
+ # Protobuf compiler / generate rule for the device management protocol.
+ 'target_name': 'device_management_proto',
+ 'type': 'none',
+ 'sources': [
+ 'device_management_backend.proto',
+ ],
+ 'rules': [
+ {
+ 'rule_name': 'genproto',
+ 'extension': 'proto',
+ 'inputs': [
+ '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)protoc<(EXECUTABLE_SUFFIX)',
+ ],
+ 'variables': {
+ # The protoc compiler requires a proto_path argument with the
+ # directory containing the .proto file. There's no generator
+ # variable that corresponds to this, so fake it.
+ 'rule_input_relpath': 'chrome/browser/policy/proto',
+ },
+ 'outputs': [
+ '<(PRODUCT_DIR)/pyproto/device_management_pb/<(RULE_INPUT_ROOT)_pb2.py',
+ '<(protoc_out_dir)/<(rule_input_relpath)/<(RULE_INPUT_ROOT).pb.h',
+ '<(protoc_out_dir)/<(rule_input_relpath)/<(RULE_INPUT_ROOT).pb.cc',
+ ],
+ 'action': [
+ '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)protoc<(EXECUTABLE_SUFFIX)',
+ '--proto_path=.',
+ './<(RULE_INPUT_ROOT)<(RULE_INPUT_EXT)',
+ '--cpp_out=<(protoc_out_dir)/<(rule_input_relpath)',
+ '--python_out=<(PRODUCT_DIR)/pyproto/device_management_pb',
+ ],
+ 'message': 'Generating C++ and Python code from <(RULE_INPUT_PATH)',
+ },
+ ],
+ 'dependencies': [
+ '../../../../third_party/protobuf/protobuf.gyp:protoc#host',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(protoc_out_dir)',
+ ]
+ },
+ },
+ {
+ 'target_name': 'device_management_proto_cpp',
+ 'type': 'none',
+ 'export_dependent_settings': [
+ '../../../../third_party/protobuf/protobuf.gyp:protobuf_lite',
+ 'device_management_proto',
+ ],
+ 'dependencies': [
+ '../../../../third_party/protobuf/protobuf.gyp:protobuf_lite',
+ 'device_management_proto',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(protoc_out_dir)',
+ ]
+ },
+ },
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 28e0b82..e0610c9 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -15,11 +15,11 @@
'chrome_resources',
'chrome_strings',
'debugger',
- 'device_management_backend_proto',
'installer_util',
'platform_locale_settings',
'profile_import',
'browser/sync/protocol/sync_proto.gyp:sync_proto_cpp',
+ 'browser/policy/proto/device_management_proto.gyp:device_management_proto_cpp',
'syncapi',
'theme_resources',
'userfeedback_proto',
@@ -4237,54 +4237,6 @@
'../third_party/protobuf/protobuf.gyp:protobuf_lite',
],
},
- {
- # Protobuf compiler / generate rule for google apps policy
- # TODO(danno): This rule shares a lot with the user feedback proto rule
- # and probably should be generalized to handle both
- 'target_name': 'device_management_backend_proto',
- 'type': 'none',
- 'sources': [
- 'browser/policy/proto/device_management_backend.proto',
- ],
- 'rules': [
- {
- 'rule_name': 'genproto',
- 'extension': 'proto',
- 'inputs': [
- '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)protoc<(EXECUTABLE_SUFFIX)',
- ],
- 'variables': {
- # The protoc compiler requires a proto_path argument with the
- # directory containing the .proto file.
- # There's no generator variable that corresponds to this, so fake it.
- 'rule_input_relpath': 'browser/policy/proto',
- },
- 'outputs': [
- '<(protoc_out_dir)/chrome/<(rule_input_relpath)/<(RULE_INPUT_ROOT).pb.h',
- '<(protoc_out_dir)/chrome/<(rule_input_relpath)/<(RULE_INPUT_ROOT).pb.cc',
- ],
- 'action': [
- '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)protoc<(EXECUTABLE_SUFFIX)',
- '--proto_path=./<(rule_input_relpath)',
- './<(rule_input_relpath)/<(RULE_INPUT_ROOT)<(RULE_INPUT_EXT)',
- '--cpp_out=<(protoc_out_dir)/chrome/<(rule_input_relpath)',
- ],
- 'message': 'Generating C++ code from <(RULE_INPUT_PATH)',
- },
- ],
- 'dependencies': [
- '../third_party/protobuf/protobuf.gyp:protobuf_lite',
- '../third_party/protobuf/protobuf.gyp:protoc#host',
- ],
- 'direct_dependent_settings': {
- 'include_dirs': [
- '<(protoc_out_dir)',
- ]
- },
- 'export_dependent_settings': [
- '../third_party/protobuf/protobuf.gyp:protobuf_lite',
- ],
- },
],
}
diff --git a/chrome/test/data/policy/device_management b/chrome/test/data/policy/device_management
new file mode 100644
index 0000000..06a49f9
--- /dev/null
+++ b/chrome/test/data/policy/device_management
@@ -0,0 +1,3 @@
+{
+ "HomepageLocation" : "http://www.chromium.org"
+}
diff --git a/net/net.gyp b/net/net.gyp
index 5283400..306ee2b 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -1174,6 +1174,7 @@
['inside_chromium_build==1', {
'dependencies': [
'../chrome/browser/sync/protocol/sync_proto.gyp:sync_proto',
+ '../chrome/browser/policy/proto/device_management_proto.gyp:device_management_proto',
'../third_party/protobuf/protobuf.gyp:py_proto',
],
}],
diff --git a/net/test/test_server.cc b/net/test/test_server.cc
index f5592a2..8e92699 100644
--- a/net/test/test_server.cc
+++ b/net/test/test_server.cc
@@ -292,6 +292,8 @@ bool TestServer::SetPythonPath() {
AppendToPythonPath(pyproto_code_dir);
AppendToPythonPath(pyproto_code_dir.Append(FILE_PATH_LITERAL("sync_pb")));
+ AppendToPythonPath(pyproto_code_dir.Append(
+ FILE_PATH_LITERAL("device_management_pb")));
return true;
}
diff --git a/net/tools/testserver/device_management.py b/net/tools/testserver/device_management.py
new file mode 100644
index 0000000..7037608
--- /dev/null
+++ b/net/tools/testserver/device_management.py
@@ -0,0 +1,318 @@
+#!/usr/bin/python2.5
+# Copyright (c) 2010 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.
+
+"""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
+from files in a directory. The files should contain policy definitions in JSON
+format, using the top-level dictionary as a key/value store. The format is
+identical to what the Linux implementation reads from /etc. Here is an example:
+
+{
+ "HomepageLocation" : "http://www.chromium.org"
+}
+
+"""
+
+import cgi
+import logging
+import random
+import re
+import sys
+
+# The name and availability of the json module varies in python versions.
+try:
+ import simplejson as json
+except ImportError:
+ try:
+ import json
+ except ImportError:
+ json = None
+
+import device_management_backend_pb2 as dm
+
+class RequestHandler(object):
+ """Decodes and handles device management requests from clients.
+
+ The handler implements all the request parsing and protobuf message decoding
+ and encoding. It calls back into the server to lookup, register, and
+ unregister clients.
+ """
+
+ def __init__(self, server, path, headers, request):
+ """Initialize the handler.
+
+ Args:
+ server: The TestServer object to use for (un)registering clients.
+ path: A string containing the request path and query parameters.
+ headers: A rfc822.Message-like object containing HTTP headers.
+ request: The request data received from the client as a string.
+ """
+ self._server = server
+ self._path = path
+ self._headers = headers
+ self._request = request
+ self._params = None
+
+ def GetUniqueParam(self, name):
+ """Extracts a unique query parameter from the request.
+
+ Args:
+ name: Names the parameter to fetch.
+ Returns:
+ The parameter value or None if the parameter doesn't exist or is not
+ unique.
+ """
+ if not self._params:
+ self._params = cgi.parse_qs(self._path[self._path.find('?')+1:])
+
+ param_list = self._params.get(name, [])
+ if len(param_list) == 1:
+ return param_list[0]
+ return None;
+
+ def HandleRequest(self):
+ """Handles a request.
+
+ Parses the data supplied at construction time and returns a pair indicating
+ http status code and response data to be sent back to the client.
+
+ Returns:
+ A tuple of HTTP status code and response data to send to the client.
+ """
+ rmsg = dm.DeviceManagementRequest()
+ rmsg.ParseFromString(self._request)
+
+ self.DumpMessage('Request', rmsg)
+
+ request_type = self.GetUniqueParam('request')
+ if request_type == 'register':
+ return self.ProcessRegister(rmsg.register_request)
+ elif request_type == 'unregister':
+ return self.ProcessUnregister(rmsg.unregister_request)
+ elif request_type == 'policy':
+ return self.ProcessPolicy(rmsg.policy_request)
+ else:
+ return (400, 'Invalid request parameter')
+
+ def ProcessRegister(self, msg):
+ """Handles a register request.
+
+ Checks the query for authorization and device identifier, registers the
+ device with the server and constructs a response.
+
+ Args:
+ msg: The DeviceRegisterRequest message received from the client.
+
+ Returns:
+ A tuple of HTTP status code and response data to send to the client.
+ """
+ # Check the auth token and device ID.
+ match = re.match('GoogleLogin auth=(\\w+)',
+ self._headers.getheader('Authorization', ''))
+ if not match:
+ return (403, 'No authorization')
+ auth_token = match.group(1)
+
+ device_id = self.GetUniqueParam('deviceid')
+ if not device_id:
+ return (400, 'Missing device identifier')
+
+ # Register the device and create a token.
+ dmtoken = self._server.RegisterDevice(device_id)
+
+ # Send back the reply.
+ response = dm.DeviceManagementResponse()
+ response.error = dm.DeviceManagementResponse.SUCCESS
+ response.register_response.device_management_token = dmtoken
+
+ self.DumpMessage('Response', response)
+
+ return (200, response.SerializeToString())
+
+ def ProcessUnregister(self, msg):
+ """Handles a register request.
+
+ Checks for authorization, unregisters the device and constructs the
+ response.
+
+ Args:
+ msg: The DeviceUnregisterRequest message received from the client.
+
+ Returns:
+ A tuple of HTTP status code and response data to send to the client.
+ """
+ # Check the management token.
+ token, response = self.CheckToken();
+ if not token:
+ return response
+
+ # Unregister the device.
+ self._server.UnregisterDevice(token);
+
+ # Prepare and send the response.
+ response = dm.DeviceManagementResponse()
+ response.error = dm.DeviceManagementResponse.SUCCESS
+ response.unregister_response.CopyFrom(dm.DeviceUnregisterResponse())
+
+ self.DumpMessage('Response', response)
+
+ return (200, response.SerializeToString())
+
+ def ProcessPolicy(self, msg):
+ """Handles a policy request.
+
+ Checks for authorization, encodes the policy into protobuf representation
+ and constructs the repsonse.
+
+ Args:
+ msg: The DevicePolicyRequest message received from the client.
+
+ Returns:
+ A tuple of HTTP status code and response data to send to the client.
+ """
+ # Check the management token.
+ token, response = self.CheckToken()
+ if not token:
+ return response
+
+ # Stuff the policy dictionary into a response message and send it back.
+ response = dm.DeviceManagementResponse()
+ response.error = dm.DeviceManagementResponse.SUCCESS
+ response.policy_response.CopyFrom(dm.DevicePolicyResponse())
+
+ # Respond only if the client requested policy for the cros/device scope,
+ # since that's where chrome policy is supposed to live in.
+ if msg.policy_scope == 'cros/device':
+ setting = response.policy_response.setting.add()
+ setting.policy_key = 'chrome-policy'
+ policy_value = dm.GenericSetting()
+ for (key, value) in self._server.policy.iteritems():
+ entry = policy_value.named_value.add()
+ entry.name = key
+ entry_value = dm.GenericValue()
+ if isinstance(value, bool):
+ entry_value.value_type = dm.GenericValue.VALUE_TYPE_BOOL
+ entry_value.bool_value = value
+ elif isinstance(value, int):
+ entry_value.value_type = dm.GenericValue.VALUE_TYPE_INT64
+ entry_value.int64_value = value
+ elif isinstance(value, str) or isinstance(value, unicode):
+ entry_value.value_type = dm.GenericValue.VALUE_TYPE_STRING
+ entry_value.string_value = value
+ elif isinstance(value, list):
+ entry_value.value_type = dm.GenericValue.VALUE_TYPE_STRING_ARRAY
+ for list_entry in value:
+ entry_value.string_array.append(str(list_entry))
+ entry.value.CopyFrom(entry_value)
+ setting.policy_value.CopyFrom(policy_value)
+
+ self.DumpMessage('Response', response)
+
+ return (200, response.SerializeToString())
+
+ def CheckToken(self):
+ """Helper for checking whether the client supplied a valid DM token.
+
+ Extracts the token from the request and passed to the server in order to
+ look up the client. Returns a pair of token and error response. If the token
+ is None, the error response is a pair of status code and error message.
+
+ Returns:
+ A pair of DM token and error response. If the token is None, the message
+ will contain the error response to send back.
+ """
+ error = None
+
+ match = re.match('GoogleDMToken token=(\\w+)',
+ self._headers.getheader('Authorization', ''))
+ if match:
+ dmtoken = match.group(1)
+ if not dmtoken:
+ error = dm.DeviceManagementResponse.DEVICE_MANAGEMENT_TOKEN_INVALID
+ elif not self._server.LookupDevice(dmtoken):
+ error = dm.DeviceManagementResponse.DEVICE_NOT_FOUND
+ else:
+ return (dmtoken, None)
+
+ response = dm.DeviceManagementResponse()
+ response.error = error
+
+ self.DumpMessage('Response', response)
+
+ return (None, (200, response.SerializeToString()))
+
+ def DumpMessage(self, label, msg):
+ """Helper for logging an ASCII dump of a protobuf message."""
+ logging.debug('%s\n%s' % (label, str(msg)))
+
+class TestServer(object):
+ """Handles requests and keeps global service state."""
+
+ def __init__(self, policy_path):
+ """Initializes the server.
+
+ Args:
+ policy_path: Names the file to read JSON-formatted policy from.
+ """
+ self._registered_devices = {}
+ self.policy = {}
+ if json is None:
+ print 'No JSON module, cannot parse policy information'
+ else :
+ try:
+ self.policy = json.loads(open(policy_path).read())
+ except IOError:
+ print 'Failed to load policy from %s' % policy_path
+
+ def HandleRequest(self, path, headers, request):
+ """Handles a request.
+
+ Args:
+ path: The request path and query parameters received from the client.
+ headers: A rfc822.Message-like object containing HTTP headers.
+ request: The request data received from the client as a string.
+ Returns:
+ A pair of HTTP status code and response data to send to the client.
+ """
+ handler = RequestHandler(self, path, headers, request)
+ return handler.HandleRequest()
+
+ def RegisterDevice(self, device_id):
+ """Registers a device and generate a DM token for it.
+
+ Args:
+ device_id: The device identifier provided by the client.
+
+ Returns:
+ The newly generated device token for the device.
+ """
+ dmtoken_chars = []
+ while len(dmtoken_chars) < 32:
+ dmtoken_chars.append(random.choice('0123456789abcdef'))
+ dmtoken= ''.join(dmtoken_chars)
+ self._registered_devices[dmtoken] = device_id
+ return dmtoken
+
+ def LookupDevice(self, dmtoken):
+ """Looks up a device by DMToken.
+
+ Args:
+ dmtoken: The device management token provided by the client.
+
+ Returns:
+ The corresponding device identifier or None if not found.
+ """
+ return self._registered_devices.get(dmtoken, None)
+
+ def UnregisterDevice(self, dmtoken):
+ """Unregisters a device identified by the given DM token.
+
+ Args:
+ dmtoken: The device management token provided by the client.
+ """
+ if dmtoken in self._registered_devices:
+ del self._registered_devices[dmtoken]
diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py
index 55aa6a9..53444e3 100755
--- a/net/tools/testserver/testserver.py
+++ b/net/tools/testserver/testserver.py
@@ -146,7 +146,8 @@ class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
self.EchoTitleHandler,
self.EchoAllHandler,
self.ChromiumSyncCommandHandler,
- self.EchoHandler] + self._get_handlers
+ self.EchoHandler,
+ self.DeviceManagementHandler] + self._get_handlers
self._put_handlers = [
self.EchoTitleHandler,
self.EchoAllHandler,
@@ -1104,6 +1105,29 @@ class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
self.wfile.write(contents)
return True
+ def DeviceManagementHandler(self):
+ """Delegates to the device management service used for cloud policy."""
+ if not self._ShouldHandleRequest("/device_management"):
+ return False
+
+ length = int(self.headers.getheader('content-length'))
+ raw_request = self.rfile.read(length)
+
+ if not self.server._device_management_handler:
+ import device_management
+ policy_path = os.path.join(self.server.data_dir, 'device_management')
+ self.server._device_management_handler = (
+ device_management.TestServer(policy_path))
+
+ http_response, raw_reply = (
+ self.server._device_management_handler.HandleRequest(self.path,
+ self.headers,
+ raw_request))
+ self.send_response(http_response)
+ self.end_headers()
+ self.wfile.write(raw_reply)
+ return True
+
def do_CONNECT(self):
for handler in self._connect_handlers:
if handler():
@@ -1199,6 +1223,7 @@ def main(options, args):
server.data_dir = MakeDataDir()
server.file_root_url = options.file_root_url
server._sync_handler = None
+ server._device_management_handler = None
# means FTP Server
else: