Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """A bare-bones test server for testing cloud policy support. | 5 """A bare-bones test server for testing cloud policy support. |
| 6 | 6 |
| 7 This implements a simple cloud policy test server that can be used to test | 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 | 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 | 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 | 10 enforced and recommended policies for the device and user scope, and a list |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 50 "robot_api_auth_code": "fake_auth_code", | 50 "robot_api_auth_code": "fake_auth_code", |
| 51 "invalidation_source": 1025, | 51 "invalidation_source": 1025, |
| 52 "invalidation_name": "UENUPOL" | 52 "invalidation_name": "UENUPOL" |
| 53 } | 53 } |
| 54 | 54 |
| 55 """ | 55 """ |
| 56 | 56 |
| 57 import base64 | 57 import base64 |
| 58 import BaseHTTPServer | 58 import BaseHTTPServer |
| 59 import cgi | 59 import cgi |
| 60 import glob | |
| 60 import google.protobuf.text_format | 61 import google.protobuf.text_format |
| 61 import hashlib | 62 import hashlib |
| 62 import logging | 63 import logging |
| 63 import os | 64 import os |
| 64 import random | 65 import random |
| 65 import re | 66 import re |
| 66 import sys | 67 import sys |
| 67 import time | 68 import time |
| 68 import tlslite | 69 import tlslite |
| 69 import tlslite.api | 70 import tlslite.api |
| (...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 270 self.headers.getheader('Authorization', '')) | 271 self.headers.getheader('Authorization', '')) |
| 271 logging.debug('oauth token -> ' + str(self.GetUniqueParam('oauth_token'))) | 272 logging.debug('oauth token -> ' + str(self.GetUniqueParam('oauth_token'))) |
| 272 logging.debug('deviceid -> ' + str(self.GetUniqueParam('deviceid'))) | 273 logging.debug('deviceid -> ' + str(self.GetUniqueParam('deviceid'))) |
| 273 self.DumpMessage('Request', rmsg) | 274 self.DumpMessage('Request', rmsg) |
| 274 | 275 |
| 275 request_type = self.GetUniqueParam('request') | 276 request_type = self.GetUniqueParam('request') |
| 276 # Check server side requirements, as defined in | 277 # Check server side requirements, as defined in |
| 277 # device_management_backend.proto. | 278 # device_management_backend.proto. |
| 278 if (self.GetUniqueParam('devicetype') != '2' or | 279 if (self.GetUniqueParam('devicetype') != '2' or |
| 279 self.GetUniqueParam('apptype') != 'Chrome' or | 280 self.GetUniqueParam('apptype') != 'Chrome' or |
| 280 (request_type != 'ping' and | 281 len(self.GetUniqueParam('deviceid')) >= 64 or |
| 281 len(self.GetUniqueParam('deviceid')) >= 64) or | |
| 282 len(self.GetUniqueParam('agent')) >= 64): | 282 len(self.GetUniqueParam('agent')) >= 64): |
| 283 return (400, 'Invalid request parameter') | 283 return (400, 'Invalid request parameter') |
| 284 if request_type == 'register': | 284 if request_type == 'register': |
| 285 response = self.ProcessRegister(rmsg.register_request) | 285 response = self.ProcessRegister(rmsg.register_request) |
| 286 elif request_type == 'api_authorization': | 286 elif request_type == 'api_authorization': |
| 287 response = self.ProcessApiAuthorization(rmsg.service_api_access_request) | 287 response = self.ProcessApiAuthorization(rmsg.service_api_access_request) |
| 288 elif request_type == 'unregister': | 288 elif request_type == 'unregister': |
| 289 response = self.ProcessUnregister(rmsg.unregister_request) | 289 response = self.ProcessUnregister(rmsg.unregister_request) |
| 290 elif request_type == 'policy' or request_type == 'ping': | 290 elif request_type == 'policy': |
| 291 response = self.ProcessPolicy(rmsg, request_type) | 291 response = self.ProcessPolicy(rmsg, request_type) |
| 292 elif request_type == 'enterprise_check': | 292 elif request_type == 'enterprise_check': |
| 293 response = self.ProcessAutoEnrollment(rmsg.auto_enrollment_request) | 293 response = self.ProcessAutoEnrollment(rmsg.auto_enrollment_request) |
| 294 elif request_type == 'device_state_retrieval': | 294 elif request_type == 'device_state_retrieval': |
| 295 response = self.ProcessDeviceStateRetrievalRequest( | 295 response = self.ProcessDeviceStateRetrievalRequest( |
| 296 rmsg.device_state_retrieval_request) | 296 rmsg.device_state_retrieval_request) |
| 297 else: | 297 else: |
| 298 return (400, 'Invalid request parameter') | 298 return (400, 'Invalid request parameter') |
| 299 | 299 |
| 300 self.DumpMessage('Response', response[1]) | 300 self.DumpMessage('Response', response[1]) |
| (...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 438 if not token_info: | 438 if not token_info: |
| 439 return error | 439 return error |
| 440 | 440 |
| 441 key_update_request = msg.device_state_key_update_request | 441 key_update_request = msg.device_state_key_update_request |
| 442 if len(key_update_request.server_backed_state_key) > 0: | 442 if len(key_update_request.server_backed_state_key) > 0: |
| 443 self.server.UpdateStateKeys(token_info['device_token'], | 443 self.server.UpdateStateKeys(token_info['device_token'], |
| 444 key_update_request.server_backed_state_key) | 444 key_update_request.server_backed_state_key) |
| 445 | 445 |
| 446 response = dm.DeviceManagementResponse() | 446 response = dm.DeviceManagementResponse() |
| 447 for request in msg.policy_request.request: | 447 for request in msg.policy_request.request: |
| 448 fetch_response = response.policy_response.response.add() | |
| 449 if (request.policy_type in | 448 if (request.policy_type in |
| 450 ('google/android/user', | 449 ('google/android/user', |
| 451 'google/chrome/extension', | |
| 452 'google/chromeos/device', | 450 'google/chromeos/device', |
| 453 'google/chromeos/publicaccount', | 451 'google/chromeos/publicaccount', |
| 454 'google/chromeos/user', | 452 'google/chromeos/user', |
| 455 'google/chrome/user', | 453 'google/chrome/user', |
| 456 'google/ios/user')): | 454 'google/ios/user')): |
| 457 if request_type != 'policy': | 455 fetch_response = response.policy_response.response.add() |
| 458 fetch_response.error_code = 400 | 456 self.ProcessCloudPolicy(request, token_info, fetch_response) |
| 459 fetch_response.error_message = 'Invalid request type' | 457 elif request.policy_type == 'google/chrome/extension': |
| 460 else: | 458 self.ProcessCloudPolicyForExtensions( |
| 461 self.ProcessCloudPolicy(request, token_info, fetch_response) | 459 request, response.policy_response, token_info) |
| 462 else: | 460 else: |
| 463 fetch_response.error_code = 400 | 461 fetch_response.error_code = 400 |
| 464 fetch_response.error_message = 'Invalid policy_type' | 462 fetch_response.error_message = 'Invalid policy_type' |
| 465 | 463 |
| 466 return (200, response) | 464 return (200, response) |
| 467 | 465 |
| 468 def ProcessAutoEnrollment(self, msg): | 466 def ProcessAutoEnrollment(self, msg): |
| 469 """Handles an auto-enrollment check request. | 467 """Handles an auto-enrollment check request. |
| 470 | 468 |
| 471 The reply depends on the value of the modulus: | 469 The reply depends on the value of the modulus: |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 525 for field in FIELDS: | 523 for field in FIELDS: |
| 526 if field in state: | 524 if field in state: |
| 527 setattr(device_state_retrieval_response, field, state[field]) | 525 setattr(device_state_retrieval_response, field, state[field]) |
| 528 | 526 |
| 529 response = dm.DeviceManagementResponse() | 527 response = dm.DeviceManagementResponse() |
| 530 response.device_state_retrieval_response.CopyFrom( | 528 response.device_state_retrieval_response.CopyFrom( |
| 531 device_state_retrieval_response) | 529 device_state_retrieval_response) |
| 532 return (200, response) | 530 return (200, response) |
| 533 | 531 |
| 534 def SetProtobufMessageField(self, group_message, field, field_value): | 532 def SetProtobufMessageField(self, group_message, field, field_value): |
| 535 '''Sets a field in a protobuf message. | 533 """Sets a field in a protobuf message. |
| 536 | 534 |
| 537 Args: | 535 Args: |
| 538 group_message: The protobuf message. | 536 group_message: The protobuf message. |
| 539 field: The field of the message to set, it should be a member of | 537 field: The field of the message to set, it should be a member of |
| 540 group_message.DESCRIPTOR.fields. | 538 group_message.DESCRIPTOR.fields. |
| 541 field_value: The value to set. | 539 field_value: The value to set. |
| 542 ''' | 540 """ |
| 543 if field.label == field.LABEL_REPEATED: | 541 if field.label == field.LABEL_REPEATED: |
| 544 assert type(field_value) == list | 542 assert type(field_value) == list |
| 545 entries = group_message.__getattribute__(field.name) | 543 entries = group_message.__getattribute__(field.name) |
| 546 if field.message_type is None: | 544 if field.message_type is None: |
| 547 for list_item in field_value: | 545 for list_item in field_value: |
| 548 entries.append(list_item) | 546 entries.append(list_item) |
| 549 else: | 547 else: |
| 550 # This field is itself a protobuf. | 548 # This field is itself a protobuf. |
| 551 sub_type = field.message_type | 549 sub_type = field.message_type |
| 552 for sub_value in field_value: | 550 for sub_value in field_value: |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 570 assert type(field_value) == list | 568 assert type(field_value) == list |
| 571 entries = group_message.__getattribute__(field.name).entries | 569 entries = group_message.__getattribute__(field.name).entries |
| 572 for list_item in field_value: | 570 for list_item in field_value: |
| 573 entries.append(list_item) | 571 entries.append(list_item) |
| 574 return | 572 return |
| 575 else: | 573 else: |
| 576 raise Exception('Unknown field type %s' % field.type) | 574 raise Exception('Unknown field type %s' % field.type) |
| 577 group_message.__setattr__(field.name, field_value) | 575 group_message.__setattr__(field.name, field_value) |
| 578 | 576 |
| 579 def GatherDevicePolicySettings(self, settings, policies): | 577 def GatherDevicePolicySettings(self, settings, policies): |
| 580 '''Copies all the policies from a dictionary into a protobuf of type | 578 """Copies all the policies from a dictionary into a protobuf of type |
| 581 CloudDeviceSettingsProto. | 579 CloudDeviceSettingsProto. |
| 582 | 580 |
| 583 Args: | 581 Args: |
| 584 settings: The destination ChromeDeviceSettingsProto protobuf. | 582 settings: The destination ChromeDeviceSettingsProto protobuf. |
| 585 policies: The source dictionary containing policies in JSON format. | 583 policies: The source dictionary containing policies in JSON format. |
| 586 ''' | 584 """ |
| 587 for group in settings.DESCRIPTOR.fields: | 585 for group in settings.DESCRIPTOR.fields: |
| 588 # Create protobuf message for group. | 586 # Create protobuf message for group. |
| 589 group_message = eval('dp.' + group.message_type.name + '()') | 587 group_message = eval('dp.' + group.message_type.name + '()') |
| 590 # Indicates if at least one field was set in |group_message|. | 588 # Indicates if at least one field was set in |group_message|. |
| 591 got_fields = False | 589 got_fields = False |
| 592 # Iterate over fields of the message and feed them from the | 590 # Iterate over fields of the message and feed them from the |
| 593 # policy config file. | 591 # policy config file. |
| 594 for field in group_message.DESCRIPTOR.fields: | 592 for field in group_message.DESCRIPTOR.fields: |
| 595 field_value = None | 593 field_value = None |
| 596 if field.name in policies: | 594 if field.name in policies: |
| 597 got_fields = True | 595 got_fields = True |
| 598 field_value = policies[field.name] | 596 field_value = policies[field.name] |
| 599 self.SetProtobufMessageField(group_message, field, field_value) | 597 self.SetProtobufMessageField(group_message, field, field_value) |
| 600 if got_fields: | 598 if got_fields: |
| 601 settings.__getattribute__(group.name).CopyFrom(group_message) | 599 settings.__getattribute__(group.name).CopyFrom(group_message) |
| 602 | 600 |
| 603 def GatherUserPolicySettings(self, settings, policies): | 601 def GatherUserPolicySettings(self, settings, policies): |
| 604 '''Copies all the policies from a dictionary into a protobuf of type | 602 """Copies all the policies from a dictionary into a protobuf of type |
| 605 CloudPolicySettings. | 603 CloudPolicySettings. |
| 606 | 604 |
| 607 Args: | 605 Args: |
| 608 settings: The destination: a CloudPolicySettings protobuf. | 606 settings: The destination: a CloudPolicySettings protobuf. |
| 609 policies: The source: a dictionary containing policies under keys | 607 policies: The source: a dictionary containing policies under keys |
| 610 'recommended' and 'mandatory'. | 608 'recommended' and 'mandatory'. |
| 611 ''' | 609 """ |
| 612 for field in settings.DESCRIPTOR.fields: | 610 for field in settings.DESCRIPTOR.fields: |
| 613 # |field| is the entry for a specific policy in the top-level | 611 # |field| is the entry for a specific policy in the top-level |
| 614 # CloudPolicySettings proto. | 612 # CloudPolicySettings proto. |
| 615 | 613 |
| 616 # Look for this policy's value in the mandatory or recommended dicts. | 614 # Look for this policy's value in the mandatory or recommended dicts. |
| 617 if field.name in policies.get('mandatory', {}): | 615 if field.name in policies.get('mandatory', {}): |
| 618 mode = cp.PolicyOptions.MANDATORY | 616 mode = cp.PolicyOptions.MANDATORY |
| 619 value = policies['mandatory'][field.name] | 617 value = policies['mandatory'][field.name] |
| 620 elif field.name in policies.get('recommended', {}): | 618 elif field.name in policies.get('recommended', {}): |
| 621 mode = cp.PolicyOptions.RECOMMENDED | 619 mode = cp.PolicyOptions.RECOMMENDED |
| 622 value = policies['recommended'][field.name] | 620 value = policies['recommended'][field.name] |
| 623 else: | 621 else: |
| 624 continue | 622 continue |
| 625 | 623 |
| 626 # Create protobuf message for this policy. | 624 # Create protobuf message for this policy. |
| 627 policy_message = eval('cp.' + field.message_type.name + '()') | 625 policy_message = eval('cp.' + field.message_type.name + '()') |
| 628 policy_message.policy_options.mode = mode | 626 policy_message.policy_options.mode = mode |
| 629 field_descriptor = policy_message.DESCRIPTOR.fields_by_name['value'] | 627 field_descriptor = policy_message.DESCRIPTOR.fields_by_name['value'] |
| 630 self.SetProtobufMessageField(policy_message, field_descriptor, value) | 628 self.SetProtobufMessageField(policy_message, field_descriptor, value) |
| 631 settings.__getattribute__(field.name).CopyFrom(policy_message) | 629 settings.__getattribute__(field.name).CopyFrom(policy_message) |
| 632 | 630 |
| 631 def ProcessCloudPolicyForExtensions(self, request, response, token_info): | |
| 632 """Handles a request for policy for extensions. | |
| 633 | |
| 634 A request for policy for extensions is slightly different from the other | |
| 635 cloud policy requests, because it can trigger 0, one or many | |
| 636 PolicyFetchResponse messages in the response. | |
| 637 | |
| 638 Args: | |
| 639 request: the PolicyFetchRequest that triggered this handler. | |
|
Mattias Nissler (ping if slow)
2014/04/28 12:02:48
nit: Capitalized The after colon (also below)
Joao da Silva
2014/04/28 12:20:10
Done.
| |
| 640 response: the DevicePolicyResponse message for the response. Multiple | |
| 641 PolicyFetchResponses will be appened to this message. | |
|
Mattias Nissler (ping if slow)
2014/04/28 12:02:48
appended
Joao da Silva
2014/04/28 12:20:10
Done.
| |
| 642 token_info: the token extracted from the request. | |
| 643 """ | |
| 644 # Send one PolicyFetchResponse for each extension that has | |
| 645 # configuration data at the server. | |
| 646 ids = self.server.ListMatchingComponents('google/chrome/extension') | |
| 647 for settings_entity_id in ids: | |
| 648 # Reuse the extension policy request, to trigger the same signature | |
| 649 # type in the response. | |
| 650 request.settings_entity_id = settings_entity_id | |
| 651 fetch_response = response.response.add() | |
| 652 self.ProcessCloudPolicy(request, token_info, fetch_response) | |
| 653 # Don't do key rotations for these messages. | |
| 654 fetch_response.ClearField('new_public_key') | |
| 655 fetch_response.ClearField('new_public_key_signature') | |
| 656 fetch_response.ClearField('new_public_key_verification_signature') | |
| 657 | |
| 633 def ProcessCloudPolicy(self, msg, token_info, response): | 658 def ProcessCloudPolicy(self, msg, token_info, response): |
| 634 """Handles a cloud policy request. (New protocol for policy requests.) | 659 """Handles a cloud policy request. (New protocol for policy requests.) |
| 635 | 660 |
| 636 Encodes the policy into protobuf representation, signs it and constructs | 661 Encodes the policy into protobuf representation, signs it and constructs |
| 637 the response. | 662 the response. |
| 638 | 663 |
| 639 Args: | 664 Args: |
| 640 msg: The CloudPolicyRequest message received from the client. | 665 msg: The CloudPolicyRequest message received from the client. |
| 641 token_info: the token extracted from the request. | 666 token_info: the token extracted from the request. |
| 642 response: A PolicyFetchResponse message that should be filled with the | 667 response: A PolicyFetchResponse message that should be filled with the |
| (...skipping 406 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1049 policy_selector: the policy type and settings entity id, joined by '/'. | 1074 policy_selector: the policy type and settings entity id, joined by '/'. |
| 1050 | 1075 |
| 1051 Returns: | 1076 Returns: |
| 1052 The filename corresponding to the policy_selector, without a file | 1077 The filename corresponding to the policy_selector, without a file |
| 1053 extension. | 1078 extension. |
| 1054 """ | 1079 """ |
| 1055 sanitized_policy_selector = re.sub('[^A-Za-z0-9.@-]', '_', policy_selector) | 1080 sanitized_policy_selector = re.sub('[^A-Za-z0-9.@-]', '_', policy_selector) |
| 1056 return os.path.join(self.data_dir or '', | 1081 return os.path.join(self.data_dir or '', |
| 1057 'policy_%s' % sanitized_policy_selector) | 1082 'policy_%s' % sanitized_policy_selector) |
| 1058 | 1083 |
| 1084 def ListMatchingComponents(self, policy_type): | |
| 1085 """Returns a list of settings entity IDs that have a configuration file. | |
| 1086 | |
| 1087 Args: | |
| 1088 policy_type: the policy type to look for. Only settings entity IDs for | |
| 1089 file selectors that match this policy_type will be returned. | |
| 1090 | |
| 1091 Returns: | |
| 1092 A list of settings entity IDs for the given |policy_type| that have a | |
| 1093 configuration file in this server (either as a .bin, .txt or .data file). | |
| 1094 """ | |
| 1095 base_name = self.GetBaseFilename(policy_type) | |
| 1096 files = glob.glob('%s_*.*' % base_name) | |
| 1097 len_base_name = len(base_name) + 1 | |
| 1098 return [ file[len_base_name:file.rfind('.')] for file in files ] | |
| 1099 | |
| 1059 def ReadPolicyFromDataDir(self, policy_selector, proto_message): | 1100 def ReadPolicyFromDataDir(self, policy_selector, proto_message): |
| 1060 """Tries to read policy payload from a file in the data directory. | 1101 """Tries to read policy payload from a file in the data directory. |
| 1061 | 1102 |
| 1062 First checks for a binary rendition of the policy protobuf in | 1103 First checks for a binary rendition of the policy protobuf in |
| 1063 <data_dir>/policy_<sanitized_policy_selector>.bin. If that exists, returns | 1104 <data_dir>/policy_<sanitized_policy_selector>.bin. If that exists, returns |
| 1064 it. If that file doesn't exist, tries | 1105 it. If that file doesn't exist, tries |
| 1065 <data_dir>/policy_<sanitized_policy_selector>.txt and decodes that as a | 1106 <data_dir>/policy_<sanitized_policy_selector>.txt and decodes that as a |
| 1066 protobuf using proto_message. If that fails as well, returns None. | 1107 protobuf using proto_message. If that fails as well, returns None. |
| 1067 | 1108 |
| 1068 Args: | 1109 Args: |
| (...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1184 if (self.options.log_to_console): | 1225 if (self.options.log_to_console): |
| 1185 logger.addHandler(logging.StreamHandler()) | 1226 logger.addHandler(logging.StreamHandler()) |
| 1186 if (self.options.log_file): | 1227 if (self.options.log_file): |
| 1187 logger.addHandler(logging.FileHandler(self.options.log_file)) | 1228 logger.addHandler(logging.FileHandler(self.options.log_file)) |
| 1188 | 1229 |
| 1189 testserver_base.TestServerRunner.run_server(self) | 1230 testserver_base.TestServerRunner.run_server(self) |
| 1190 | 1231 |
| 1191 | 1232 |
| 1192 if __name__ == '__main__': | 1233 if __name__ == '__main__': |
| 1193 sys.exit(PolicyServerRunner().main()) | 1234 sys.exit(PolicyServerRunner().main()) |
| OLD | NEW |