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

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: serialized list protos -> repeated fields 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
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..050381fe0c7edd63002efcff329d237c561f08cb 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,39 @@
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 should
+contain a dictionary with three top-level keys. Keys "enforced" and
+"recommended" hold sub-dictionaries with policy definitions in JSON
+format as key/value stores. Their format is identical to what the Linux
+implementation reads from /etc. "managed_users" holds a list of 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. Here is an example:
{
- "HomepageLocation" : "http://www.chromium.org"
+ "enforced": {
+ "HomepageLocation" : "http://www.chromium.org"
+ },
+ "recommended": {
+ "JavascriptEnabled": false,
+ },
+ "managed_users": [
+ "secret123456"
+ ]
}
+
"""
+import calendar
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 +51,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 +115,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.
@@ -113,9 +153,8 @@ class RequestHandler(object):
# Check the auth token and device ID.
match = re.match('GoogleLogin auth=(\\w+)',
self._headers.getheader('Authorization', ''))
Mattias Nissler (ping if slow) 2011/01/28 10:29:34 Don't need that regex check any longer, no?
gfeher 2011/01/28 13:42:10 Done.
- 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 +167,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,6 +202,39 @@ 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.
@@ -214,6 +287,104 @@ 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.type == field.TYPE_STRING and field.label == field.LABEL_REPEATED:
+ assert type(field_value) == list
+ 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)
Mattias Nissler (ping if slow) 2011/01/28 10:29:34 Could this also handle string lists? In other word
gfeher 2011/01/28 13:42:10 I get this error when I try to use __setattr__ to
+
+ 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.
Jakob Kummerow 2011/01/28 10:45:52 nit: response
gfeher 2011/01/28 13:42:10 Done.
+
+ 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 == 'chromeos/device':
+ pass
Mattias Nissler (ping if slow) 2011/01/28 10:29:34 What's this for?
gfeher 2011/01/28 13:42:10 Forgotten stuff -> I'll add support for device and
+
+ for group in settings.DESCRIPTOR.fields:
+ # Create protobuf message for group.
+ group_message = eval('cp.' + group.message_type.name + '()')
+ # Indiactes if at least one field was set in |group_message|.
Jakob Kummerow 2011/01/28 10:45:52 nit: Indicates
gfeher 2011/01/28 13:42:10 Done.
+ got_fields = False
+ # Indicates if the current group is recommended (and not enforced.)
+ # A group will be considered recommended if all of its present members
+ # are set to recommended in the policy config file.
+ recommended = True
Mattias Nissler (ping if slow) 2011/01/28 10:29:34 can't you just use a PolicyMode enum directly?
gfeher 2011/01/28 13:42:10 Done.
+ # 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 self._server.policy['enforced']:
+ got_fields = True
+ recommended = False
+ field_value = self._server.policy['enforced'][field.name]
+ elif field.name in self._server.policy['recommended']:
+ got_fields = True
+ field_value = self._server.policy['recommended'][field.name]
+ if field_value != None:
+ self.SetProtobufMessageField(group_message, field, field_value)
+ if got_fields:
+ if recommended:
+ group_message.policy_mode = cp.RECOMMENDED
+ settings.__getattribute__(group.name).CopyFrom(group_message)
+
+ # Construct response
+ signed_response = dm.SignedCloudPolicyResponse()
+ signed_response.settings.CopyFrom(settings)
+ signed_response.timestamp = calendar.timegm(time.gmtime())
Jakob Kummerow 2011/01/28 10:45:52 you can remove 'import calendar' and just use 'sig
gfeher 2011/01/28 13:42:10 Done.
+ 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 +425,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 +443,17 @@ class TestServer(object):
except IOError:
print 'Failed to load policy from %s' % policy_path
+ self.cert_chain = []
+ for cert_path in policy_cert_chain:
+ try:
+ last_cert = open(cert_path).read()
+ except IOError:
+ print 'Failed to load certificate from %s' % cert_path
+ certificate = tlslite.api.X509()
+ certificate.parse(last_cert)
+ self.cert_chain.append(certificate)
+ self.private_key = tlslite.api.parsePEMKey(last_cert, private=True)
+
def HandleRequest(self, path, headers, request):
"""Handles a request.

Powered by Google App Engine
This is Rietveld 408576698