summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
Diffstat (limited to 'net')
-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
4 files changed, 347 insertions, 1 deletions
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: