Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(26)

Unified Diff: net/tools/testserver/device_management.py

Issue 6161007: New protocol and testserver for the Chrome-DMServer protocol (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: address comments + some cleanup Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/test/test_server.cc ('k') | net/tools/testserver/testserver.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: net/tools/testserver/device_management.py
diff --git a/net/tools/testserver/device_management.py b/net/tools/testserver/device_management.py
index d71522741a950dcc2cf2eab2ca6373a7bf10fd52..183451bb515bbc1bbe1a79437f3324dd5249d88f 100644
--- a/net/tools/testserver/device_management.py
+++ b/net/tools/testserver/device_management.py
@@ -1,5 +1,5 @@
#!/usr/bin/python2.5
-# Copyright (c) 2010 The Chromium Authors. All rights reserved.
+# Copyright (c) 2011 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.
@@ -7,21 +7,47 @@
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:
+the file named device_management in the server's data directory. It contains
+enforced and recommended 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. Other keys in the root dictionary identify request
+scopes. Each request scope is described by a dictionary that holds two
+sub-dictionaries: "mandatory" and "recommended". Both these hold the policy
+definitions as key/value stores, their format is identical to what the Linux
+implementation reads from /etc.
+
+Example:
{
- "HomepageLocation" : "http://www.chromium.org"
+ "chromeos/device": {
+ "mandatory": {
+ "HomepageLocation" : "http://www.chromium.org"
+ },
+ "recommended": {
+ "JavascriptEnabled": false,
+ },
+ },
+ "managed_users": [
+ "secret123456"
+ ]
}
+
"""
import cgi
import logging
+import os
import random
import re
import sys
+import time
+import tlslite
+import tlslite.api
# The name and availability of the json module varies in python versions.
try:
@@ -33,6 +59,8 @@ except ImportError:
json = None
import device_management_backend_pb2 as dm
+import cloud_policy_pb2 as cp
+
class RequestHandler(object):
"""Decodes and handles device management requests from clients.
@@ -95,9 +123,29 @@ class RequestHandler(object):
return self.ProcessUnregister(rmsg.unregister_request)
elif request_type == 'policy':
return self.ProcessPolicy(rmsg.policy_request)
+ elif request_type == 'cloud_policy':
+ return self.ProcessCloudPolicyRequest(rmsg.cloud_policy_request)
+ elif request_type == 'managed_check':
+ return self.ProcessManagedCheck(rmsg.managed_check_request)
else:
return (400, 'Invalid request parameter')
+ def CheckGoogleLogin(self):
+ """Extracts the GoogleLogin auth token from the HTTP request, and
+ returns it. Returns None if the token is not present.
+ """
+ match = re.match('GoogleLogin auth=(\\w+)',
+ self._headers.getheader('Authorization', ''))
+ if not match:
+ return None
+ return match.group(1)
+
+ def GetDeviceName(self):
+ """Returns the name for the currently authenticated device based on its
+ device id.
+ """
+ return 'chromeos-' + self.GetUniqueParam('deviceid')
+
def ProcessRegister(self, msg):
"""Handles a register request.
@@ -111,11 +159,8 @@ class RequestHandler(object):
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:
+ if not self.CheckGoogleLogin():
return (403, 'No authorization')
- auth_token = match.group(1)
device_id = self.GetUniqueParam('deviceid')
if not device_id:
@@ -128,6 +173,7 @@ class RequestHandler(object):
response = dm.DeviceManagementResponse()
response.error = dm.DeviceManagementResponse.SUCCESS
response.register_response.device_management_token = dmtoken
+ response.register_response.device_name = self.GetDeviceName()
self.DumpMessage('Response', response)
@@ -162,11 +208,44 @@ class RequestHandler(object):
return (200, response.SerializeToString())
+ def ProcessManagedCheck(self, msg):
+ """Handles a 'managed check' request.
+
+ Queries the list of managed users and responds the client if their user
+ is managed or not.
+
+ Args:
+ msg: The ManagedCheckRequest message received from the client.
+
+ Returns:
+ A tuple of HTTP status code and response data to send to the client.
+ """
+ # Check the management token.
+ auth = self.CheckGoogleLogin()
+ if not auth:
+ return (403, 'No authorization')
+
+ managed_check_response = dm.ManagedCheckResponse()
+ if ('*' in self._server.policy['managed_users'] or
+ auth in self._server.policy['managed_users']):
+ managed_check_response.mode = dm.ManagedCheckResponse.MANAGED;
+ else:
+ managed_check_response.mode = dm.ManagedCheckResponse.UNMANAGED;
+
+ # Prepare and send the response.
+ response = dm.DeviceManagementResponse()
+ response.error = dm.DeviceManagementResponse.SUCCESS
+ response.managed_check_response.CopyFrom(managed_check_response)
+
+ 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.
+ and constructs the response.
Args:
msg: The DevicePolicyRequest message received from the client.
@@ -214,6 +293,113 @@ class RequestHandler(object):
return (200, response.SerializeToString())
+ def SetProtobufMessageField(self, group_message, field, field_value):
+ '''Sets a field in a protobuf message.
+
+ Args:
+ group_message: The protobuf message.
+ field: The field of the message to set, it shuold be a member of
+ group_message.DESCRIPTOR.fields.
+ field_value: The value to set.
+ '''
+ if field.label == field.LABEL_REPEATED:
+ assert type(field_value) == list
+ assert field.type == field.TYPE_STRING
+ list_field = group_message.__getattribute__(field.name)
+ for list_item in field_value:
+ list_field.append(list_item)
+ else:
+ # Simple cases:
+ if field.type == field.TYPE_BOOL:
+ assert type(field_value) == bool
+ elif field.type == field.TYPE_STRING:
+ assert type(field_value) == str
+ elif field.type == field.TYPE_INT64:
+ assert type(field_value) == int
+ else:
+ raise Exception('Unknown field type %s' % field.type_name)
+ group_message.__setattr__(field.name, field_value)
+
+ def GatherPolicySettings(self, settings, policies):
+ '''Copies all the policies from a dictionary into a protobuf of type
+ CloudPolicySettings.
+
+ Args:
+ settings: The destination: a CloudPolicySettings protobuf.
+ policies: The source: a dictionary containing policies under keys
+ 'recommended' and 'mandatory'.
+ '''
+ for group in settings.DESCRIPTOR.fields:
+ # Create protobuf message for group.
+ group_message = eval('cp.' + group.message_type.name + '()')
+ # We assume that this policy group will be recommended, and only switch
+ # it to mandatory if at least one of its members is mandatory.
+ group_message.policy_options.mode = cp.PolicyOptions.RECOMMENDED
+ # Indicates if at least one field was set in |group_message|.
+ got_fields = False
+ # Iterate over fields of the message and feed them from the
+ # policy config file.
+ for field in group_message.DESCRIPTOR.fields:
+ field_value = None
+ if field.name in policies['mandatory']:
+ group_message.policy_options.mode = cp.PolicyOptions.MANDATORY
+ field_value = policies['mandatory'][field.name]
+ elif field.name in policies['recommended']:
+ field_value = policies['recommended'][field.name]
+ if field_value != None:
+ got_fields = True
+ self.SetProtobufMessageField(group_message, field, field_value)
+ if got_fields:
+ settings.__getattribute__(group.name).CopyFrom(group_message)
+
+ def ProcessCloudPolicyRequest(self, msg):
+ """Handles a cloud policy request. (New protocol for policy requests.)
+
+ Checks for authorization, encodes the policy into protobuf representation,
+ signs it and constructs the repsonse.
+
+ Args:
+ msg: The CloudPolicyRequest message received from the client.
+
+ Returns:
+ A tuple of HTTP status code and response data to send to the client.
+ """
+ token, response = self.CheckToken()
+ if not token:
+ return response
+
+ settings = cp.CloudPolicySettings()
+
+ if msg.policy_scope in self._server.policy:
+ # Respond is only given if the scope is specified in the config file.
+ # Normally 'chromeos/device' and 'chromeos/user' should be accepted.
+ self.GatherPolicySettings(settings,
+ self._server.policy[msg.policy_scope])
+
+ # Construct response
+ signed_response = dm.SignedCloudPolicyResponse()
+ signed_response.settings.CopyFrom(settings)
+ signed_response.timestamp = int(time.time())
+ signed_response.request_token = token;
+ signed_response.device_name = self.GetDeviceName()
+
+ cloud_response = dm.CloudPolicyResponse()
+ cloud_response.signed_response = signed_response.SerializeToString()
+ signed_data = cloud_response.signed_response
+ cloud_response.signature = (
+ self._server.private_key.hashAndSign(signed_data).tostring())
+ for certificate in self._server.cert_chain:
+ cloud_response.certificate_chain.append(
+ certificate.writeBytes().tostring())
+
+ response = dm.DeviceManagementResponse()
+ response.error = dm.DeviceManagementResponse.SUCCESS
+ response.cloud_policy_response.CopyFrom(cloud_response)
+
+ self.DumpMessage('Response', response)
+
+ return (200, response.SerializeToString())
+
def CheckToken(self):
"""Helper for checking whether the client supplied a valid DM token.
@@ -254,11 +440,13 @@ class RequestHandler(object):
class TestServer(object):
"""Handles requests and keeps global service state."""
- def __init__(self, policy_path):
+ def __init__(self, policy_path, policy_cert_chain):
"""Initializes the server.
Args:
policy_path: Names the file to read JSON-formatted policy from.
+ policy_cert_chain: List of paths to X.509 certificate files of the
+ certificate chain used for signing responses.
"""
self._registered_devices = {}
self.policy = {}
@@ -270,6 +458,20 @@ class TestServer(object):
except IOError:
print 'Failed to load policy from %s' % policy_path
+ self.private_key = None
+ self.cert_chain = []
+ for cert_path in policy_cert_chain:
+ try:
+ cert_text = open(cert_path).read()
+ except IOError:
+ print 'Failed to load certificate from %s' % cert_path
+ certificate = tlslite.api.X509()
+ certificate.parse(cert_text)
+ self.cert_chain.append(certificate)
+ if self.private_key is None:
+ self.private_key = tlslite.api.parsePEMKey(cert_text, private=True)
+ assert self.private_key != None
+
def HandleRequest(self, path, headers, request):
"""Handles a request.
« no previous file with comments | « net/test/test_server.cc ('k') | net/tools/testserver/testserver.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698