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 |