| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 """A bare-bones test server for testing cloud policy support. | |
| 6 | |
| 7 This implements a simple cloud policy test server that can be used to test | |
| 8 chrome's device management service client. The policy information is read from | |
| 9 the file named device_management in the server's data directory. It contains | |
| 10 enforced and recommended policies for the device and user scope, and a list | |
| 11 of managed users. | |
| 12 | |
| 13 The format of the file is JSON. The root dictionary contains a list under the | |
| 14 key "managed_users". It contains auth tokens for which the server will claim | |
| 15 that the user is managed. The token string "*" indicates that all users are | |
| 16 claimed to be managed. Other keys in the root dictionary identify request | |
| 17 scopes. The user-request scope is described by a dictionary that holds two | |
| 18 sub-dictionaries: "mandatory" and "recommended". Both these hold the policy | |
| 19 definitions as key/value stores, their format is identical to what the Linux | |
| 20 implementation reads from /etc. | |
| 21 The device-scope holds the policy-definition directly as key/value stores in the | |
| 22 protobuf-format. | |
| 23 | |
| 24 Example: | |
| 25 | |
| 26 { | |
| 27 "google/chromeos/device" : { | |
| 28 "guest_mode_enabled" : false | |
| 29 }, | |
| 30 "google/chromeos/user" : { | |
| 31 "mandatory" : { | |
| 32 "HomepageLocation" : "http://www.chromium.org", | |
| 33 "IncognitoEnabled" : false | |
| 34 }, | |
| 35 "recommended" : { | |
| 36 "JavascriptEnabled": false | |
| 37 } | |
| 38 }, | |
| 39 "google/chromeos/publicaccount/user@example.com" : { | |
| 40 "mandatory" : { | |
| 41 "HomepageLocation" : "http://www.chromium.org" | |
| 42 }, | |
| 43 "recommended" : { | |
| 44 } | |
| 45 }, | |
| 46 "managed_users" : [ | |
| 47 "secret123456" | |
| 48 ] | |
| 49 } | |
| 50 | |
| 51 """ | |
| 52 | |
| 53 import cgi | |
| 54 import hashlib | |
| 55 import logging | |
| 56 import os | |
| 57 import random | |
| 58 import re | |
| 59 import sys | |
| 60 import time | |
| 61 import tlslite | |
| 62 import tlslite.api | |
| 63 import tlslite.utils | |
| 64 | |
| 65 # The name and availability of the json module varies in python versions. | |
| 66 try: | |
| 67 import simplejson as json | |
| 68 except ImportError: | |
| 69 try: | |
| 70 import json | |
| 71 except ImportError: | |
| 72 json = None | |
| 73 | |
| 74 import asn1der | |
| 75 import device_management_backend_pb2 as dm | |
| 76 import cloud_policy_pb2 as cp | |
| 77 import chrome_device_policy_pb2 as dp | |
| 78 | |
| 79 # ASN.1 object identifier for PKCS#1/RSA. | |
| 80 PKCS1_RSA_OID = '\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01' | |
| 81 | |
| 82 # SHA256 sum of "0". | |
| 83 SHA256_0 = hashlib.sha256('0').digest() | |
| 84 | |
| 85 # List of bad machine identifiers that trigger the |valid_serial_number_missing| | |
| 86 # flag to be set set in the policy fetch response. | |
| 87 BAD_MACHINE_IDS = [ '123490EN400015' ]; | |
| 88 | |
| 89 # List of machines that trigger the server to send kiosk enrollment response | |
| 90 # for the register request. | |
| 91 KIOSK_MACHINE_IDS = [ 'KIOSK' ]; | |
| 92 | |
| 93 class RequestHandler(object): | |
| 94 """Decodes and handles device management requests from clients. | |
| 95 | |
| 96 The handler implements all the request parsing and protobuf message decoding | |
| 97 and encoding. It calls back into the server to lookup, register, and | |
| 98 unregister clients. | |
| 99 """ | |
| 100 | |
| 101 def __init__(self, server, path, headers, request): | |
| 102 """Initialize the handler. | |
| 103 | |
| 104 Args: | |
| 105 server: The TestServer object to use for (un)registering clients. | |
| 106 path: A string containing the request path and query parameters. | |
| 107 headers: A rfc822.Message-like object containing HTTP headers. | |
| 108 request: The request data received from the client as a string. | |
| 109 """ | |
| 110 self._server = server | |
| 111 self._path = path | |
| 112 self._headers = headers | |
| 113 self._request = request | |
| 114 self._params = None | |
| 115 | |
| 116 def GetUniqueParam(self, name): | |
| 117 """Extracts a unique query parameter from the request. | |
| 118 | |
| 119 Args: | |
| 120 name: Names the parameter to fetch. | |
| 121 Returns: | |
| 122 The parameter value or None if the parameter doesn't exist or is not | |
| 123 unique. | |
| 124 """ | |
| 125 if not self._params: | |
| 126 self._params = cgi.parse_qs(self._path[self._path.find('?') + 1:]) | |
| 127 | |
| 128 param_list = self._params.get(name, []) | |
| 129 if len(param_list) == 1: | |
| 130 return param_list[0] | |
| 131 return None; | |
| 132 | |
| 133 def HandleRequest(self): | |
| 134 """Handles a request. | |
| 135 | |
| 136 Parses the data supplied at construction time and returns a pair indicating | |
| 137 http status code and response data to be sent back to the client. | |
| 138 | |
| 139 Returns: | |
| 140 A tuple of HTTP status code and response data to send to the client. | |
| 141 """ | |
| 142 rmsg = dm.DeviceManagementRequest() | |
| 143 rmsg.ParseFromString(self._request) | |
| 144 | |
| 145 logging.debug('gaia auth token -> ' + | |
| 146 self._headers.getheader('Authorization', '')) | |
| 147 logging.debug('oauth token -> ' + str(self.GetUniqueParam('oauth_token'))) | |
| 148 logging.debug('deviceid -> ' + str(self.GetUniqueParam('deviceid'))) | |
| 149 self.DumpMessage('Request', rmsg) | |
| 150 | |
| 151 request_type = self.GetUniqueParam('request') | |
| 152 # Check server side requirements, as defined in | |
| 153 # device_management_backend.proto. | |
| 154 if (self.GetUniqueParam('devicetype') != '2' or | |
| 155 self.GetUniqueParam('apptype') != 'Chrome' or | |
| 156 (request_type != 'ping' and | |
| 157 len(self.GetUniqueParam('deviceid')) >= 64) or | |
| 158 len(self.GetUniqueParam('agent')) >= 64): | |
| 159 return (400, 'Invalid request parameter') | |
| 160 if request_type == 'register': | |
| 161 return self.ProcessRegister(rmsg.register_request) | |
| 162 elif request_type == 'unregister': | |
| 163 return self.ProcessUnregister(rmsg.unregister_request) | |
| 164 elif request_type == 'policy' or request_type == 'ping': | |
| 165 return self.ProcessPolicy(rmsg.policy_request, request_type) | |
| 166 elif request_type == 'enterprise_check': | |
| 167 return self.ProcessAutoEnrollment(rmsg.auto_enrollment_request) | |
| 168 else: | |
| 169 return (400, 'Invalid request parameter') | |
| 170 | |
| 171 def CheckGoogleLogin(self): | |
| 172 """Extracts the auth token from the request and returns it. The token may | |
| 173 either be a GoogleLogin token from an Authorization header, or an OAuth V2 | |
| 174 token from the oauth_token query parameter. Returns None if no token is | |
| 175 present. | |
| 176 """ | |
| 177 oauth_token = self.GetUniqueParam('oauth_token') | |
| 178 if oauth_token: | |
| 179 return oauth_token | |
| 180 | |
| 181 match = re.match('GoogleLogin auth=(\\w+)', | |
| 182 self._headers.getheader('Authorization', '')) | |
| 183 if match: | |
| 184 return match.group(1) | |
| 185 | |
| 186 return None | |
| 187 | |
| 188 def ProcessRegister(self, msg): | |
| 189 """Handles a register request. | |
| 190 | |
| 191 Checks the query for authorization and device identifier, registers the | |
| 192 device with the server and constructs a response. | |
| 193 | |
| 194 Args: | |
| 195 msg: The DeviceRegisterRequest message received from the client. | |
| 196 | |
| 197 Returns: | |
| 198 A tuple of HTTP status code and response data to send to the client. | |
| 199 """ | |
| 200 # Check the auth token and device ID. | |
| 201 auth = self.CheckGoogleLogin() | |
| 202 if not auth: | |
| 203 return (403, 'No authorization') | |
| 204 | |
| 205 policy = self._server.GetPolicies() | |
| 206 if ('*' not in policy['managed_users'] and | |
| 207 auth not in policy['managed_users']): | |
| 208 return (403, 'Unmanaged') | |
| 209 | |
| 210 device_id = self.GetUniqueParam('deviceid') | |
| 211 if not device_id: | |
| 212 return (400, 'Missing device identifier') | |
| 213 | |
| 214 token_info = self._server.RegisterDevice(device_id, | |
| 215 msg.machine_id, | |
| 216 msg.type) | |
| 217 | |
| 218 # Send back the reply. | |
| 219 response = dm.DeviceManagementResponse() | |
| 220 response.register_response.device_management_token = ( | |
| 221 token_info['device_token']) | |
| 222 response.register_response.machine_name = token_info['machine_name'] | |
| 223 response.register_response.enrollment_type = token_info['enrollment_mode'] | |
| 224 | |
| 225 self.DumpMessage('Response', response) | |
| 226 | |
| 227 return (200, response.SerializeToString()) | |
| 228 | |
| 229 def ProcessUnregister(self, msg): | |
| 230 """Handles a register request. | |
| 231 | |
| 232 Checks for authorization, unregisters the device and constructs the | |
| 233 response. | |
| 234 | |
| 235 Args: | |
| 236 msg: The DeviceUnregisterRequest message received from the client. | |
| 237 | |
| 238 Returns: | |
| 239 A tuple of HTTP status code and response data to send to the client. | |
| 240 """ | |
| 241 # Check the management token. | |
| 242 token, response = self.CheckToken(); | |
| 243 if not token: | |
| 244 return response | |
| 245 | |
| 246 # Unregister the device. | |
| 247 self._server.UnregisterDevice(token['device_token']); | |
| 248 | |
| 249 # Prepare and send the response. | |
| 250 response = dm.DeviceManagementResponse() | |
| 251 response.unregister_response.CopyFrom(dm.DeviceUnregisterResponse()) | |
| 252 | |
| 253 self.DumpMessage('Response', response) | |
| 254 | |
| 255 return (200, response.SerializeToString()) | |
| 256 | |
| 257 def ProcessPolicy(self, msg, request_type): | |
| 258 """Handles a policy request. | |
| 259 | |
| 260 Checks for authorization, encodes the policy into protobuf representation | |
| 261 and constructs the response. | |
| 262 | |
| 263 Args: | |
| 264 msg: The DevicePolicyRequest message received from the client. | |
| 265 | |
| 266 Returns: | |
| 267 A tuple of HTTP status code and response data to send to the client. | |
| 268 """ | |
| 269 for request in msg.request: | |
| 270 if (request.policy_type in | |
| 271 ('google/chrome/user', | |
| 272 'google/chromeos/user', | |
| 273 'google/chromeos/device', | |
| 274 'google/chromeos/publicaccount')): | |
| 275 if request_type != 'policy': | |
| 276 return (400, 'Invalid request type') | |
| 277 else: | |
| 278 return self.ProcessCloudPolicy(request) | |
| 279 else: | |
| 280 return (400, 'Invalid policy_type') | |
| 281 | |
| 282 def ProcessAutoEnrollment(self, msg): | |
| 283 """Handles an auto-enrollment check request. | |
| 284 | |
| 285 The reply depends on the value of the modulus: | |
| 286 1: replies with no new modulus and the sha256 hash of "0" | |
| 287 2: replies with a new modulus, 4. | |
| 288 4: replies with a new modulus, 2. | |
| 289 8: fails with error 400. | |
| 290 16: replies with a new modulus, 16. | |
| 291 32: replies with a new modulus, 1. | |
| 292 anything else: replies with no new modulus and an empty list of hashes | |
| 293 | |
| 294 These allow the client to pick the testing scenario its wants to simulate. | |
| 295 | |
| 296 Args: | |
| 297 msg: The DeviceAutoEnrollmentRequest message received from the client. | |
| 298 | |
| 299 Returns: | |
| 300 A tuple of HTTP status code and response data to send to the client. | |
| 301 """ | |
| 302 auto_enrollment_response = dm.DeviceAutoEnrollmentResponse() | |
| 303 | |
| 304 if msg.modulus == 1: | |
| 305 auto_enrollment_response.hash.append(SHA256_0) | |
| 306 elif msg.modulus == 2: | |
| 307 auto_enrollment_response.expected_modulus = 4 | |
| 308 elif msg.modulus == 4: | |
| 309 auto_enrollment_response.expected_modulus = 2 | |
| 310 elif msg.modulus == 8: | |
| 311 return (400, 'Server error') | |
| 312 elif msg.modulus == 16: | |
| 313 auto_enrollment_response.expected_modulus = 16 | |
| 314 elif msg.modulus == 32: | |
| 315 auto_enrollment_response.expected_modulus = 1 | |
| 316 | |
| 317 response = dm.DeviceManagementResponse() | |
| 318 response.auto_enrollment_response.CopyFrom(auto_enrollment_response) | |
| 319 return (200, response.SerializeToString()) | |
| 320 | |
| 321 def SetProtobufMessageField(self, group_message, field, field_value): | |
| 322 '''Sets a field in a protobuf message. | |
| 323 | |
| 324 Args: | |
| 325 group_message: The protobuf message. | |
| 326 field: The field of the message to set, it should be a member of | |
| 327 group_message.DESCRIPTOR.fields. | |
| 328 field_value: The value to set. | |
| 329 ''' | |
| 330 if field.label == field.LABEL_REPEATED: | |
| 331 assert type(field_value) == list | |
| 332 entries = group_message.__getattribute__(field.name) | |
| 333 if field.message_type is None: | |
| 334 for list_item in field_value: | |
| 335 entries.append(list_item) | |
| 336 else: | |
| 337 # This field is itself a protobuf. | |
| 338 sub_type = field.message_type | |
| 339 for sub_value in field_value: | |
| 340 assert type(sub_value) == dict | |
| 341 # Add a new sub-protobuf per list entry. | |
| 342 sub_message = entries.add() | |
| 343 # Now iterate over its fields and recursively add them. | |
| 344 for sub_field in sub_message.DESCRIPTOR.fields: | |
| 345 if sub_field.name in sub_value: | |
| 346 value = sub_value[sub_field.name] | |
| 347 self.SetProtobufMessageField(sub_message, sub_field, value) | |
| 348 return | |
| 349 elif field.type == field.TYPE_BOOL: | |
| 350 assert type(field_value) == bool | |
| 351 elif field.type == field.TYPE_STRING: | |
| 352 assert type(field_value) == str or type(field_value) == unicode | |
| 353 elif field.type == field.TYPE_INT64: | |
| 354 assert type(field_value) == int | |
| 355 elif (field.type == field.TYPE_MESSAGE and | |
| 356 field.message_type.name == 'StringList'): | |
| 357 assert type(field_value) == list | |
| 358 entries = group_message.__getattribute__(field.name).entries | |
| 359 for list_item in field_value: | |
| 360 entries.append(list_item) | |
| 361 return | |
| 362 else: | |
| 363 raise Exception('Unknown field type %s' % field.type) | |
| 364 group_message.__setattr__(field.name, field_value) | |
| 365 | |
| 366 def GatherDevicePolicySettings(self, settings, policies): | |
| 367 '''Copies all the policies from a dictionary into a protobuf of type | |
| 368 CloudDeviceSettingsProto. | |
| 369 | |
| 370 Args: | |
| 371 settings: The destination ChromeDeviceSettingsProto protobuf. | |
| 372 policies: The source dictionary containing policies in JSON format. | |
| 373 ''' | |
| 374 for group in settings.DESCRIPTOR.fields: | |
| 375 # Create protobuf message for group. | |
| 376 group_message = eval('dp.' + group.message_type.name + '()') | |
| 377 # Indicates if at least one field was set in |group_message|. | |
| 378 got_fields = False | |
| 379 # Iterate over fields of the message and feed them from the | |
| 380 # policy config file. | |
| 381 for field in group_message.DESCRIPTOR.fields: | |
| 382 field_value = None | |
| 383 if field.name in policies: | |
| 384 got_fields = True | |
| 385 field_value = policies[field.name] | |
| 386 self.SetProtobufMessageField(group_message, field, field_value) | |
| 387 if got_fields: | |
| 388 settings.__getattribute__(group.name).CopyFrom(group_message) | |
| 389 | |
| 390 def GatherUserPolicySettings(self, settings, policies): | |
| 391 '''Copies all the policies from a dictionary into a protobuf of type | |
| 392 CloudPolicySettings. | |
| 393 | |
| 394 Args: | |
| 395 settings: The destination: a CloudPolicySettings protobuf. | |
| 396 policies: The source: a dictionary containing policies under keys | |
| 397 'recommended' and 'mandatory'. | |
| 398 ''' | |
| 399 for field in settings.DESCRIPTOR.fields: | |
| 400 # |field| is the entry for a specific policy in the top-level | |
| 401 # CloudPolicySettings proto. | |
| 402 | |
| 403 # Look for this policy's value in the mandatory or recommended dicts. | |
| 404 if field.name in policies.get('mandatory', {}): | |
| 405 mode = cp.PolicyOptions.MANDATORY | |
| 406 value = policies['mandatory'][field.name] | |
| 407 elif field.name in policies.get('recommended', {}): | |
| 408 mode = cp.PolicyOptions.RECOMMENDED | |
| 409 value = policies['recommended'][field.name] | |
| 410 else: | |
| 411 continue | |
| 412 | |
| 413 # Create protobuf message for this policy. | |
| 414 policy_message = eval('cp.' + field.message_type.name + '()') | |
| 415 policy_message.policy_options.mode = mode | |
| 416 field_descriptor = policy_message.DESCRIPTOR.fields_by_name['value'] | |
| 417 self.SetProtobufMessageField(policy_message, field_descriptor, value) | |
| 418 settings.__getattribute__(field.name).CopyFrom(policy_message) | |
| 419 | |
| 420 def ProcessCloudPolicy(self, msg): | |
| 421 """Handles a cloud policy request. (New protocol for policy requests.) | |
| 422 | |
| 423 Checks for authorization, encodes the policy into protobuf representation, | |
| 424 signs it and constructs the repsonse. | |
| 425 | |
| 426 Args: | |
| 427 msg: The CloudPolicyRequest message received from the client. | |
| 428 | |
| 429 Returns: | |
| 430 A tuple of HTTP status code and response data to send to the client. | |
| 431 """ | |
| 432 | |
| 433 token_info, error = self.CheckToken() | |
| 434 if not token_info: | |
| 435 return error | |
| 436 | |
| 437 if msg.machine_id: | |
| 438 self._server.UpdateMachineId(token_info['device_token'], msg.machine_id) | |
| 439 | |
| 440 # Response is only given if the scope is specified in the config file. | |
| 441 # Normally 'google/chromeos/device', 'google/chromeos/user' and | |
| 442 # 'google/chromeos/publicaccount' should be accepted. | |
| 443 policy = self._server.GetPolicies() | |
| 444 policy_value = '' | |
| 445 policy_key = msg.policy_type | |
| 446 if msg.settings_entity_id: | |
| 447 policy_key += '/' + msg.settings_entity_id | |
| 448 if msg.policy_type in token_info['allowed_policy_types']: | |
| 449 if (msg.policy_type == 'google/chromeos/user' or | |
| 450 msg.policy_type == 'google/chrome/user' or | |
| 451 msg.policy_type == 'google/chromeos/publicaccount'): | |
| 452 settings = cp.CloudPolicySettings() | |
| 453 self.GatherUserPolicySettings(settings, policy.get(policy_key, {})) | |
| 454 elif msg.policy_type == 'google/chromeos/device': | |
| 455 settings = dp.ChromeDeviceSettingsProto() | |
| 456 self.GatherDevicePolicySettings(settings, policy.get(policy_key, {})) | |
| 457 | |
| 458 # Figure out the key we want to use. If multiple keys are configured, the | |
| 459 # server will rotate through them in a round-robin fashion. | |
| 460 signing_key = None | |
| 461 req_key = None | |
| 462 key_version = 1 | |
| 463 nkeys = len(self._server.keys) | |
| 464 if msg.signature_type == dm.PolicyFetchRequest.SHA1_RSA and nkeys > 0: | |
| 465 if msg.public_key_version in range(1, nkeys + 1): | |
| 466 # requested key exists, use for signing and rotate. | |
| 467 req_key = self._server.keys[msg.public_key_version - 1]['private_key'] | |
| 468 key_version = (msg.public_key_version % nkeys) + 1 | |
| 469 signing_key = self._server.keys[key_version - 1] | |
| 470 | |
| 471 # Fill the policy data protobuf. | |
| 472 policy_data = dm.PolicyData() | |
| 473 policy_data.policy_type = msg.policy_type | |
| 474 policy_data.timestamp = int(time.time() * 1000) | |
| 475 policy_data.request_token = token_info['device_token'] | |
| 476 policy_data.policy_value = settings.SerializeToString() | |
| 477 policy_data.machine_name = token_info['machine_name'] | |
| 478 policy_data.valid_serial_number_missing = ( | |
| 479 token_info['machine_id'] in BAD_MACHINE_IDS) | |
| 480 policy_data.settings_entity_id = msg.settings_entity_id | |
| 481 | |
| 482 if signing_key: | |
| 483 policy_data.public_key_version = key_version | |
| 484 if msg.policy_type == 'google/chromeos/publicaccount': | |
| 485 policy_data.username = msg.settings_entity_id | |
| 486 else: | |
| 487 # For regular user/device policy, there is no way for the testserver to | |
| 488 # know the user name belonging to the GAIA auth token we received (short | |
| 489 # of actually talking to GAIA). To address this, we read the username from | |
| 490 # the policy configuration dictionary, or use a default. | |
| 491 policy_data.username = policy.get('policy_user', 'user@example.com') | |
| 492 policy_data.device_id = token_info['device_id'] | |
| 493 signed_data = policy_data.SerializeToString() | |
| 494 | |
| 495 response = dm.DeviceManagementResponse() | |
| 496 fetch_response = response.policy_response.response.add() | |
| 497 fetch_response.policy_data = signed_data | |
| 498 if signing_key: | |
| 499 fetch_response.policy_data_signature = ( | |
| 500 signing_key['private_key'].hashAndSign(signed_data).tostring()) | |
| 501 if msg.public_key_version != key_version: | |
| 502 fetch_response.new_public_key = signing_key['public_key'] | |
| 503 if req_key: | |
| 504 fetch_response.new_public_key_signature = ( | |
| 505 req_key.hashAndSign(fetch_response.new_public_key).tostring()) | |
| 506 | |
| 507 self.DumpMessage('Response', response) | |
| 508 | |
| 509 return (200, response.SerializeToString()) | |
| 510 | |
| 511 def CheckToken(self): | |
| 512 """Helper for checking whether the client supplied a valid DM token. | |
| 513 | |
| 514 Extracts the token from the request and passed to the server in order to | |
| 515 look up the client. | |
| 516 | |
| 517 Returns: | |
| 518 A pair of token information record and error response. If the first | |
| 519 element is None, then the second contains an error code to send back to | |
| 520 the client. Otherwise the first element is the same structure that is | |
| 521 returned by LookupToken(). | |
| 522 """ | |
| 523 error = 500 | |
| 524 dmtoken = None | |
| 525 request_device_id = self.GetUniqueParam('deviceid') | |
| 526 match = re.match('GoogleDMToken token=(\\w+)', | |
| 527 self._headers.getheader('Authorization', '')) | |
| 528 if match: | |
| 529 dmtoken = match.group(1) | |
| 530 if not dmtoken: | |
| 531 error = 401 | |
| 532 else: | |
| 533 token_info = self._server.LookupToken(dmtoken) | |
| 534 if (not token_info or | |
| 535 not request_device_id or | |
| 536 token_info['device_id'] != request_device_id): | |
| 537 error = 410 | |
| 538 else: | |
| 539 return (token_info, None) | |
| 540 | |
| 541 logging.debug('Token check failed with error %d' % error) | |
| 542 | |
| 543 return (None, (error, 'Server error %d' % error)) | |
| 544 | |
| 545 def DumpMessage(self, label, msg): | |
| 546 """Helper for logging an ASCII dump of a protobuf message.""" | |
| 547 logging.debug('%s\n%s' % (label, str(msg))) | |
| 548 | |
| 549 class TestServer(object): | |
| 550 """Handles requests and keeps global service state.""" | |
| 551 | |
| 552 def __init__(self, policy_path, private_key_paths): | |
| 553 """Initializes the server. | |
| 554 | |
| 555 Args: | |
| 556 policy_path: Names the file to read JSON-formatted policy from. | |
| 557 private_key_paths: List of paths to read private keys from. | |
| 558 """ | |
| 559 self._registered_tokens = {} | |
| 560 self.policy_path = policy_path | |
| 561 | |
| 562 self.keys = [] | |
| 563 if private_key_paths: | |
| 564 # Load specified keys from the filesystem. | |
| 565 for key_path in private_key_paths: | |
| 566 try: | |
| 567 key = tlslite.api.parsePEMKey(open(key_path).read(), private=True) | |
| 568 except IOError: | |
| 569 print 'Failed to load private key from %s' % key_path | |
| 570 continue | |
| 571 | |
| 572 assert key is not None | |
| 573 self.keys.append({ 'private_key' : key }) | |
| 574 else: | |
| 575 # Generate a key if none were specified. | |
| 576 key = tlslite.api.generateRSAKey(1024) | |
| 577 assert key is not None | |
| 578 self.keys.append({ 'private_key' : key }) | |
| 579 | |
| 580 # Derive the public keys from the loaded private keys. | |
| 581 for entry in self.keys: | |
| 582 key = entry['private_key'] | |
| 583 | |
| 584 algorithm = asn1der.Sequence( | |
| 585 [ asn1der.Data(asn1der.OBJECT_IDENTIFIER, PKCS1_RSA_OID), | |
| 586 asn1der.Data(asn1der.NULL, '') ]) | |
| 587 rsa_pubkey = asn1der.Sequence([ asn1der.Integer(key.n), | |
| 588 asn1der.Integer(key.e) ]) | |
| 589 pubkey = asn1der.Sequence([ algorithm, asn1der.Bitstring(rsa_pubkey) ]) | |
| 590 entry['public_key'] = pubkey; | |
| 591 | |
| 592 def GetPolicies(self): | |
| 593 """Returns the policies to be used, reloaded form the backend file every | |
| 594 time this is called. | |
| 595 """ | |
| 596 policy = {} | |
| 597 if json is None: | |
| 598 print 'No JSON module, cannot parse policy information' | |
| 599 else : | |
| 600 try: | |
| 601 policy = json.loads(open(self.policy_path).read()) | |
| 602 except IOError: | |
| 603 print 'Failed to load policy from %s' % self.policy_path | |
| 604 return policy | |
| 605 | |
| 606 def HandleRequest(self, path, headers, request): | |
| 607 """Handles a request. | |
| 608 | |
| 609 Args: | |
| 610 path: The request path and query parameters received from the client. | |
| 611 headers: A rfc822.Message-like object containing HTTP headers. | |
| 612 request: The request data received from the client as a string. | |
| 613 Returns: | |
| 614 A pair of HTTP status code and response data to send to the client. | |
| 615 """ | |
| 616 handler = RequestHandler(self, path, headers, request) | |
| 617 return handler.HandleRequest() | |
| 618 | |
| 619 def RegisterDevice(self, device_id, machine_id, type): | |
| 620 """Registers a device or user and generates a DM token for it. | |
| 621 | |
| 622 Args: | |
| 623 device_id: The device identifier provided by the client. | |
| 624 | |
| 625 Returns: | |
| 626 The newly generated device token for the device. | |
| 627 """ | |
| 628 dmtoken_chars = [] | |
| 629 while len(dmtoken_chars) < 32: | |
| 630 dmtoken_chars.append(random.choice('0123456789abcdef')) | |
| 631 dmtoken = ''.join(dmtoken_chars) | |
| 632 allowed_policy_types = { | |
| 633 dm.DeviceRegisterRequest.BROWSER: ['google/chrome/user'], | |
| 634 dm.DeviceRegisterRequest.USER: ['google/chromeos/user'], | |
| 635 dm.DeviceRegisterRequest.DEVICE: [ | |
| 636 'google/chromeos/device', | |
| 637 'google/chromeos/publicaccount' | |
| 638 ], | |
| 639 dm.DeviceRegisterRequest.TT: ['google/chromeos/user', | |
| 640 'google/chrome/user'], | |
| 641 } | |
| 642 if machine_id in KIOSK_MACHINE_IDS: | |
| 643 enrollment_mode = dm.DeviceRegisterResponse.RETAIL | |
| 644 else: | |
| 645 enrollment_mode = dm.DeviceRegisterResponse.ENTERPRISE | |
| 646 self._registered_tokens[dmtoken] = { | |
| 647 'device_id': device_id, | |
| 648 'device_token': dmtoken, | |
| 649 'allowed_policy_types': allowed_policy_types[type], | |
| 650 'machine_name': 'chromeos-' + machine_id, | |
| 651 'machine_id': machine_id, | |
| 652 'enrollment_mode': enrollment_mode, | |
| 653 } | |
| 654 return self._registered_tokens[dmtoken] | |
| 655 | |
| 656 def UpdateMachineId(self, dmtoken, machine_id): | |
| 657 """Updates the machine identifier for a registered device. | |
| 658 | |
| 659 Args: | |
| 660 dmtoken: The device management token provided by the client. | |
| 661 machine_id: Updated hardware identifier value. | |
| 662 """ | |
| 663 if dmtoken in self._registered_tokens: | |
| 664 self._registered_tokens[dmtoken]['machine_id'] = machine_id | |
| 665 | |
| 666 def LookupToken(self, dmtoken): | |
| 667 """Looks up a device or a user by DM token. | |
| 668 | |
| 669 Args: | |
| 670 dmtoken: The device management token provided by the client. | |
| 671 | |
| 672 Returns: | |
| 673 A dictionary with information about a device or user that is registered by | |
| 674 dmtoken, or None if the token is not found. | |
| 675 """ | |
| 676 return self._registered_tokens.get(dmtoken, None) | |
| 677 | |
| 678 def UnregisterDevice(self, dmtoken): | |
| 679 """Unregisters a device identified by the given DM token. | |
| 680 | |
| 681 Args: | |
| 682 dmtoken: The device management token provided by the client. | |
| 683 """ | |
| 684 if dmtoken in self._registered_tokens.keys(): | |
| 685 del self._registered_tokens[dmtoken] | |
| OLD | NEW |