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

Side by Side Diff: chrome/browser/policy/test/policy_testserver.py

Issue 233423002: Don't upload extension IDs in the cloud policy protocol. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixed nits Created 6 years, 7 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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])
301 return (response[0], response[1].SerializeToString()) 301 return (response[0], response[1].SerializeToString())
302 302
303 def CreatePolicyForExternalPolicyData(self, policy_key): 303 def CreatePolicyForExternalPolicyData(self, policy_key):
304 """Returns an ExternalPolicyData protobuf for policy_key. 304 """Returns an ExternalPolicyData protobuf for policy_key.
305 305
306 If there is policy data for policy_key then the download url will be 306 If there is policy data for policy_key then the download url will be
307 set so that it points to that data, and the appropriate hash is also set. 307 set so that it points to that data, and the appropriate hash is also set.
308 Otherwise, the protobuf will be empty. 308 Otherwise, the protobuf will be empty.
309 309
310 Args: 310 Args:
311 policy_key: the policy type and settings entity id, joined by '/'. 311 policy_key: The policy type and settings entity id, joined by '/'.
312 312
313 Returns: 313 Returns:
314 A serialized ExternalPolicyData. 314 A serialized ExternalPolicyData.
315 """ 315 """
316 settings = ep.ExternalPolicyData() 316 settings = ep.ExternalPolicyData()
317 data = self.server.ReadPolicyDataFromDataDir(policy_key) 317 data = self.server.ReadPolicyDataFromDataDir(policy_key)
318 if data: 318 if data:
319 settings.download_url = urlparse.urljoin( 319 settings.download_url = urlparse.urljoin(
320 self.server.GetBaseURL(), 'externalpolicydata?key=%s' % policy_key) 320 self.server.GetBaseURL(), 'externalpolicydata?key=%s' % policy_key)
321 settings.secure_hash = hashlib.sha256(data).digest() 321 settings.secure_hash = hashlib.sha256(data).digest()
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after
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
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
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.
640 response: The DevicePolicyResponse message for the response. Multiple
641 PolicyFetchResponses will be appended to this message.
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
643 response data. 668 response data.
644 """ 669 """
645 670
646 if msg.machine_id: 671 if msg.machine_id:
647 self.server.UpdateMachineId(token_info['device_token'], msg.machine_id) 672 self.server.UpdateMachineId(token_info['device_token'], msg.machine_id)
648 673
649 # Response is only given if the scope is specified in the config file. 674 # Response is only given if the scope is specified in the config file.
650 # Normally 'google/chromeos/device', 'google/chromeos/user' and 675 # Normally 'google/chromeos/device', 'google/chromeos/user' and
651 # 'google/chromeos/publicaccount' should be accepted. 676 # 'google/chromeos/publicaccount' should be accepted.
(...skipping 387 matching lines...) Expand 10 before | Expand all | Expand 10 after
1039 def WriteClientState(self): 1064 def WriteClientState(self):
1040 """Writes the client state back to the file.""" 1065 """Writes the client state back to the file."""
1041 if self.client_state_file is not None: 1066 if self.client_state_file is not None:
1042 json_data = json.dumps(self._registered_tokens) 1067 json_data = json.dumps(self._registered_tokens)
1043 open(self.client_state_file, 'w').write(json_data) 1068 open(self.client_state_file, 'w').write(json_data)
1044 1069
1045 def GetBaseFilename(self, policy_selector): 1070 def GetBaseFilename(self, policy_selector):
1046 """Returns the base filename for the given policy_selector. 1071 """Returns the base filename for the given policy_selector.
1047 1072
1048 Args: 1073 Args:
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
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())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698