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. |