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 7This implements a simple cloud policy test server that can be used to test 8chrome's device management service client. The policy information is read from 9the file named device_management in the server's data directory. It contains 10enforced and recommended policies for the device and user scope, and a list 11of managed users. 12 13The format of the file is JSON. The root dictionary contains a list under the 14key "managed_users". It contains auth tokens for which the server will claim 15that the user is managed. The token string "*" indicates that all users are 16claimed to be managed. Other keys in the root dictionary identify request 17scopes. The user-request scope is described by a dictionary that holds two 18sub-dictionaries: "mandatory" and "recommended". Both these hold the policy 19definitions as key/value stores, their format is identical to what the Linux 20implementation reads from /etc. 21The device-scope holds the policy-definition directly as key/value stores in the 22protobuf-format. 23 24Example: 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 "current_key_index": 0, 50 "robot_api_auth_code": "", 51 "token_enrollment": { 52 "token": "abcd-ef01-123123123", 53 "username": "admin@example.com" 54 }, 55 "expected_errors": { 56 "register": 500, 57 } 58 "allow_set_device_attributes" : false, 59 "initial_enrollment_state": { 60 "TEST_serial": { 61 "initial_enrollment_mode": 2, 62 "management_domain": "test-domain.com", 63 "is_license_packaged_with_device": true 64 } 65 } 66} 67 68""" 69 70import base64 71from six.moves import BaseHTTPServer 72import cgi 73import glob 74import google.protobuf.text_format 75import hashlib 76import json 77import logging 78import os 79import random 80import re 81import six 82import sys 83import time 84import tlslite 85import tlslite.api 86import tlslite.utils 87import tlslite.utils.cryptomath 88from six.moves import urllib 89from six.moves.urllib import request as urllib_request 90from six.moves.urllib import parse as urlparse 91 92import asn1der 93import testserver_base 94 95import device_management_backend_pb2 as dm 96import cloud_policy_pb2 as cp 97import policy_common_definitions_pb2 as cd 98 99# Policy for extensions is not supported on Android. 100try: 101 import chrome_extension_policy_pb2 as ep 102except ImportError: 103 ep = None 104 105# Device policy is only available on Chrome OS builds. 106try: 107 import chrome_device_policy_pb2 as dp 108except ImportError: 109 dp = None 110 111# pyopenssl is only reliably available on Chrome OS builds. 112# This is currently OK because policy_testserver.py's support for certificate 113# provisioning is only used in Tast test for now. 114# TODO(https://bugs.chromium.org/p/chromium/issues/detail?id=1101729): Switch 115# to issuing certificates in the test.. 116try: 117 from OpenSSL import crypto 118except ImportError: 119 crypto = None 120 121# ASN.1 object identifier for PKCS#1/RSA. 122PKCS1_RSA_OID = b'\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01' 123 124# List of machines that trigger the server to send kiosk enrollment response 125# for the register request. 126KIOSK_MACHINE_IDS = [ 'KIOSK' ] 127 128# Dictionary containing base64-encoded policy signing keys plus per-domain 129# signatures. Format is: 130# { 131# 'key': <base64-encoded PKCS8-format private key>, 132# 'signatures': { 133# <domain1>: <base64-encdoded SHA256 signature for key + domain1> 134# <domain2>: <base64-encdoded SHA256 signature for key + domain2> 135# ... 136# } 137# } 138SIGNING_KEYS = [ 139 # Key1 140 {'key': 141 'MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2c3KzcPqvnJ5HCk3OZkf1' 142 'LMO8Ht4dw4FO2U0EmKvpo0zznj4RwUdmKobH1AFWzwZP4CDY2M67MsukE/1Jnbx1QIDAQ' 143 'ABAkBkKcLZa/75hHVz4PR3tZaw34PATlfxEG6RiRIwXlf/FFlfGIZOSxdW/I1A3XRl0/9' 144 'nZMuctBSKBrcTRZQWfT/hAiEA9g8xbQbMO6BEH/XCRSsQbPlvj4c9wDtVEzeAzZ/ht9kC' 145 'IQDiml+/lXS1emqml711jJcYJNYJzdy1lL/ieKogR59oXQIhAK+Pl4xa1U2VxAWpq7r+R' 146 'vH55wdZT03hB4p2h4gvEzXBAiAkw9kvE0eZPiBZoRrrHIFTOH7FnnHlwBmV2+/2RsiVPQ' 147 'IhAKqx/4qisivvmoM/xbzUagfoxwsu1A/4mGjhBKiS0BCq', 148 'signatures': 149 {'example.com': 150 'l+sT5mziei/GbmiP7VtRCCfwpZcg7uKbW2OlnK5B/TTELutjEIAMdHduNBwbO44qOn' 151 '/5c7YrtkXbBehaaDYFPGI6bGTbDmG9KRxhS+DaB7opgfCQWLi79Gn/jytKLZhRN/VS' 152 'y+PEbezqMi3d1/xDxlThwWZDNwnhv9ER/Nu/32ZTjzgtqonSn2CQtwXCIILm4FdV/1' 153 '/BdmZG+Ge4i4FTqYtInir5YFe611KXU/AveGhQGBIAXo4qYg1IqbVrvKBSU9dlI6Sl' 154 '9TJJLbJ3LGaXuljgFhyMAl3gcy7ftC9MohEmwa+sc7y2mOAgYQ5SSmyAtQwQgAkX9J' 155 '3+tfxjmoA/dg==', 156 'chromepolicytest.com': 157 'TzBiigZKwBdr6lyP6tUDsw+Q9wYO1Yepyxm0O4JZ4RID32L27sWzC1/hwC51fRcCvP' 158 'luEVIW6mH+BFODXMrteUFWfbbG7jgV+Wg+QdzMqgJjxhNKFXPTsZ7/286LAd1vBY/A' 159 'nGd8Wog6AhzfrgMbLNsH794GD0xIUwRvXUWFNP8pClj5VPgQnJrIA9aZwW8FNGbteA' 160 'HacFB0T/oqP5s7XT4Qvkj14RLmCgTwEM8Vcpqy5teJaF8yN17wniveddoOQGH6s0HC' 161 'ocprEccrH5fP/WVAPxCfx4vVYQY5q4CZ4K3f6dTC2FV4IDelM6dugEkvSS02YCzDaO' 162 'N+Z7IwElzTKg==', 163 'managedchrome.com': 164 'T0wXC5w3GXyovA09pyOLX7ui/NI603UfbZXYyTbHI7xtzCIaHVPH35Nx4zdqVrdsej' 165 'ErQ12yVLDDIJokY4Yl+/fj/zrkAPxThI+TNQ+jo0i+al05PuopfpzvCzIXiZBbkbyW' 166 '3XfedxXP3IPN2XU2/3vX+ZXUNG6pxeETem64kGezkjkUraqnHw3JVzwJYHhpMcwdLP' 167 'PYK6V23BbEHEVBtQZd/ledXacz7gOzm1zGni4e+vxA2roAdJWyhbjU0dTKNNUsZmMv' 168 'ryQH9Af1Jw+dqs0RAbhcJXm2i8EUWIgNv6aMn1Z2DzZwKKjXsKgcYSRo8pdYa8RZAo' 169 'UExd9roA9a5w==', 170 } 171 }, 172 # Key2 173 {'key': 174 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmZhreV04M3knCi6wibr49' 175 'oDesHny1G33PKOX9ko8pcxAiu9ZqsKCj7wNW2PGqnLi81fddACwQtYn5xdhCtzB9wIDAQ' 176 'ABAkA0z8m0cy8N08xundspoFZWO71WJLgv/peSDBYGI0RzJR1l9Np355EukQUQwRs5XrL' 177 '3vRQZy2vDqeiR96epkAhRAiEAzJ4DVI8k3pAl7CGv5icqFkJ02viExIwehhIEXBcB6p0C' 178 'IQDAKmzpoRpBEZRQ9xrTvPOi+Ea8Jnd478BU7CI/LFfgowIgMfLIoVWoDGRnvXKju60Hy' 179 'xNB70oHLut9cADp64j6QMkCIDrgxN4QbmrhaAAmtiGKE1wrlgCwCIsVamiasSOKAqLhAi' 180 'EAo/ItVcFtQPod97qG71CY/O4JzOciuU6AMhprs181vfM=', 181 'signatures': 182 # Key2 signatures 183 {'example.com': 184 'cO0nQjRptkeefKDw5QpJSQDavHABxUvbR9Wvoa235OG9Whw1RFqq2ye6pKnI3ezW6/' 185 '7b4ANcpi5a7HV5uF8K7gWyYdxY8NHLeyrbwXxg5j6HAmHmkP1UZcf/dAnWqo7cW8g4' 186 'DIQOhC43KkveMYJ2HnelwdXt/7zqkbe8/3Yj4nhjAUeARx86Sb8Nzydwkrvqs5Jw/x' 187 '5LG+BODExrXXcGu/ubDlW4ivJFqfNUPQysqBXSMY2XCHPJDx3eECLGVVN/fFAWWgjM' 188 'HFObAriAt0b18cc9Nr0mAt4Qq1oDzWcAHCPHE+5dr8Uf46BUrMLJRNRKCY7rrsoIin' 189 '9Be9gs3W+Aww==', 190 'chromepolicytest.com': 191 'mr+9CCYvR0cTvPwlzkxqlpGYy55gY7cPiIkPAPoql51yHK1tkMTOSFru8Dy/nMt+0o' 192 '4z7WO60F1wnIBGkQxnTj/DsO6QpCYi7oHqtLmZ2jsLQFlMyvPGUtpJEFvRwjr/TNbh' 193 '6RqUtz1LQFuJQ848kBrx7nkte1L8SuPDExgx+Q3LtbNj4SuTdvMUBMvEERXiLuwfFL' 194 'BefGjtsqfWETQVlJTCW7xcqOLedIX8UYgEDBpDOZ23A3GzCShuBsIut5m87R5mODht' 195 'EUmKNDK1+OMc6SyDpf+r48Wph4Db1bVaKy8fcpSNJOwEgsrmH7/+owKPGcN7I5jYAF' 196 'Z2PGxHTQ9JNA==', 197 'managedchrome.com': 198 'o5MVSo4bRwIJ/aooGyXpRXsEsWPG8fNA2UTG8hgwnLYhNeJCCnLs/vW2vdp0URE8jn' 199 'qiG4N8KjbuiGw0rJtO1EygdLfpnMEtqYlFjrOie38sy92l/AwohXj6luYzMWL+FqDu' 200 'WQeXasjgyY4s9BOLQVDEnEj3pvqhrk/mXvMwUeXGpbxTNbWAd0C8BTZrGOwU/kIXxo' 201 'vAMGg8L+rQaDwBTEnMsMZcvlrIyqSg5v4BxCWuL3Yd2xvUqZEUWRp1aKetsHRnz5hw' 202 'H7WK7DzvKepDn06XjPG9lchi448U3HB3PRKtCzfO3nD9YXMKTuqRpKPF8PeK11CWh1' 203 'DBvBYwi20vbQ==', 204 }, 205 }, 206] 207 208INVALID_ENROLLMENT_TOKEN = 'invalid_enrollment_token' 209 210POLICY_COMMON_DEFINITIONS_TYPES = [ 211 'StringList', 212 'PolicyOptions', 213 'BooleanPolicyProto', 214 'IntegerPolicyProto', 215 'StringPolicyProto', 216 'StringListPolicyProto' 217] 218 219# Private key used for issuing certificates for issuing certificates 220# for the built-in certificate provisioning feature. 221CERT_PROVISIONING_CA_PRIVATE_KEY_PEM="""\ 222-----BEGIN RSA PRIVATE KEY----- 223MIIEpAIBAAKCAQEAyh01PGm57kraP7TJpCtGEpvSx6OwKJmqCQp/kmr9hfFTrWW3 224W4QgdmprzmYNt8vxtJkRLCz9/4K1lk1hfoDu5Qpx5VaNZ0bcwfzRG/PxcB9+XzZp 225gppqpyq1HACYudqPi9wsxw1qSrB4Av1W+paOpdM6/blchwJpRuOmrb1iwooVYmR/ 226CnSDIEppPk5I8iwN3VTq3cSErDrC4Xtquw3jHs4aaq55ZcD31WRumiTkZY6oBZsH 227gzFsy+uCqwfv8aIA6wU0nPo5tfq0xzIjDhAA/ZSkSx05UrEk1S7rgvpRIJMZ/+Pi 228Jz/VX32XOBXyrGuwD+pS9zvtsPQ2rzz9aPhDFwIDAQABAoIBAH8tPdBT3rD43Lf1 229dGQe7qrK7ii88R27A2lI99kUBY8AuVyEgonNa/fXIxru0Hb0l5TCNDIN5Y2fm8+F 230xXEqhCgPGHfsrHFt/375LENgjm21A3m57U5HCBFEKE4EehWIV4bz9iESae2xePK4 231osBveDcT4SzCNFynwcLfgIQWhUxPI7TlwEkR79vcM+l6CtbUVxUW+wTTS/Zp72FM 232sBBNsXIBB0yHh0m30vg43jv3apBaZxogAPx2crWOu1A2NK20gQPgrCIHIjn1Of4e 233JvWSwKFnmF9UzwDQ1KZo25EX4BEirVYYlk84Eq9Ds2ojOVD+mCEfbrEsqPorYl+c 234F2d9CQECgYEA96kRVjhrS/Hbo5KNUOnOBs0uD8hCJ0cGlTRAvkwhGAENolewIuyj 235wwXGfkAV0Rs3qf4HjzVeEiX1QegsHxqFPOhjuDK34pH6caU6UI1R6BAr9H6QmEO9 23653LlKvlX+6kJ+RjXxvmftRFWgPhY526IbXgY4judI1+LR9FPmzyCLEECgYEA0OuD 237Oc//mZomtnBVFiw0RiamERTMsWO6G7QVDs814utHaFVzJWkqVn6eIns5NgfyZ0ZE 238xand7/wUtz0YM0wvdZPllhL0zXSujfqScO3qeE3XLPspv4dom+jdgwr1uR8rE5Av 2398qeLrZaItSWDMKL2+1QX0Frn3k1cMO/wiw+w+VcCgYEAgtK5SL1W2HAzIK3anmJT 240Jb6e1VFouIzJSmmmxZ87YA22YQpHDbvJKczUNH6vx5zEA7Uf0yNSxO1uJ9l37Ro6 241RZlQi82m2zVXgU7RhhmQqbBZN7bftL8cArXrno7GTjbWANKBsSbNmX1GH6yQcfgu 242cv0cz+zDrhrbXR2RGqSU8sECgYBmf3VVMsfq+ycNENWd2DgZRrLo5HR8fzn6h4Jh 243TqXYW6gf9vRUIWFlKB+7OQtbh9CUfHQXKfy51cnwEGhEGpeaLuJPm6NA/YL6Izof 244b4o+VapA5kSYM/3NqBStSv49QZ5nrbDocuzjUFxnyyyu+vUDX0GDtmXVucyGMeGo 245yB0CZwKBgQDZ8RwInbSZbAo2/fjIxFGxxV0tSRH1n5L27QB5jzikXW+6jBOYRDQh 246fHXdC808L+jJ0zgOBlJbbCM3TliiVqDE6Lcc3GShA1mrjvGmAy05e1ejgGZYX7c5 247C97TFZS6CD+9uC2FV4RWJuO56kCGlDVLI3/iwIThtywvDt0qKnSsGA== 248-----END RSA PRIVATE KEY-----""" 249 250# The obfuscated_customer_id that will be served in device policy PolicyData 251# responses. 252OBFUSCATED_CUSTOMER_ID = 'policy_testserver_customer_id' 253 254class PolicyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 255 """Decodes and handles device management requests from clients. 256 257 The handler implements all the request parsing and protobuf message decoding 258 and encoding. It calls back into the server to lookup, register, and 259 unregister clients. 260 """ 261 262 def __init__(self, request, client_address, server): 263 """Initialize the handler. 264 265 Args: 266 request: The request data received from the client as a string. 267 client_address: The client address. 268 server: The TestServer object to use for (un)registering clients. 269 """ 270 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request, 271 client_address, server) 272 273 def GetUniqueParam(self, name): 274 """Extracts a unique query parameter from the request. 275 276 Args: 277 name: Names the parameter to fetch. 278 Returns: 279 The parameter value or None if the parameter doesn't exist or is not 280 unique. 281 """ 282 if not hasattr(self, '_params'): 283 self._params = cgi.parse_qs(self.path[self.path.find('?') + 1:]) 284 285 param_list = self._params.get(name, []) 286 if len(param_list) == 1: 287 return param_list[0] 288 return None 289 290 def do_GET(self): 291 """Handles GET requests. 292 293 Currently this is only used to serve external policy data.""" 294 sep = self.path.find('?') 295 path = self.path if sep == -1 else self.path[:sep] 296 if path == '/externalpolicydata': 297 http_response, raw_reply = self.HandleExternalPolicyDataRequest() 298 elif path == '/configuration/test/exit': 299 # This is not part of the standard DM server protocol. 300 # This extension is added to make the test server exit gracefully 301 # when the test is complete. 302 self.server.stop = True 303 http_response = 200 304 raw_reply = 'OK' 305 elif path == '/test/ping': 306 # This path and reply are used by the test setup of host-driven tests for 307 # Android to determine if the server is up, and are not part of the 308 # DM protocol. 309 http_response = 200 310 raw_reply = 'Policy server is up.' 311 else: 312 http_response = 404 313 raw_reply = 'Invalid path' 314 self.send_response(http_response) 315 self.end_headers() 316 if six.PY3 and isinstance(raw_reply, str): 317 raw_reply = raw_reply.encode() 318 self.wfile.write(raw_reply) 319 320 def do_POST(self): 321 http_response, raw_reply = self.HandleRequest() 322 self.send_response(http_response) 323 if (http_response == 200): 324 self.send_header('Content-Type', 'application/x-protobuffer') 325 self.end_headers() 326 if six.PY3 and isinstance(raw_reply, str): 327 raw_reply = raw_reply.encode() 328 self.wfile.write(raw_reply) 329 330 def HandleExternalPolicyDataRequest(self): 331 """Handles a request to download policy data for a component.""" 332 policy_key = self.GetUniqueParam('key') 333 if not policy_key: 334 return (400, 'Missing key parameter') 335 data = self.server.ReadPolicyDataFromDataDir(policy_key) 336 if data is None: 337 return (404, 'Policy not found for ' + policy_key) 338 return (200, data) 339 340 def HandleRequest(self): 341 """Handles a request. 342 343 Parses the data supplied at construction time and returns a pair indicating 344 http status code and response data to be sent back to the client. 345 346 Returns: 347 A tuple of HTTP status code and response data to send to the client. 348 """ 349 rmsg = dm.DeviceManagementRequest() 350 length = int(self.headers.get('content-length')) 351 rmsg.ParseFromString(self.rfile.read(length)) 352 353 logging.debug('gaia auth token -> ' + 354 self.headers.get('Authorization', '')) 355 logging.debug('oauth token -> ' + str(self.GetUniqueParam('oauth_token'))) 356 logging.debug('deviceid -> ' + str(self.GetUniqueParam('deviceid'))) 357 self.DumpMessage('Request', rmsg) 358 359 request_type = self.GetUniqueParam('request') 360 # Check server side requirements, as defined in 361 # device_management_backend.proto. 362 if (self.GetUniqueParam('devicetype') != '2' or 363 self.GetUniqueParam('apptype') != 'Chrome' or 364 (self.GetUniqueParam('deviceid') is not None and 365 len(self.GetUniqueParam('deviceid')) >= 64)): 366 return (400, 'Invalid request parameter') 367 368 expected_error = self.GetExpectedError(request_type) 369 if expected_error: 370 return expected_error 371 372 if request_type == 'register': 373 response = self.ProcessRegister(rmsg.register_request) 374 elif request_type == 'certificate_based_register': 375 response = self.ProcessCertBasedRegister( 376 rmsg.certificate_based_register_request) 377 elif request_type == 'api_authorization': 378 response = self.ProcessApiAuthorization(rmsg.service_api_access_request) 379 elif request_type == 'unregister': 380 response = self.ProcessUnregister(rmsg.unregister_request) 381 elif request_type == 'policy': 382 response = self.ProcessPolicy(rmsg, request_type) 383 elif request_type == 'enterprise_check': 384 response = self.ProcessAutoEnrollment(rmsg.auto_enrollment_request) 385 elif request_type == 'device_initial_enrollment_state': 386 response = self.ProcessDeviceInitialEnrollmentState( 387 rmsg.device_initial_enrollment_state_request) 388 elif request_type == 'device_state_retrieval': 389 response = self.ProcessDeviceStateRetrievalRequest( 390 rmsg.device_state_retrieval_request) 391 elif request_type == 'status_upload': 392 response = self.ProcessStatusUploadRequest( 393 rmsg.device_status_report_request, rmsg.session_status_report_request) 394 elif request_type == 'device_attribute_update_permission': 395 response = self.ProcessDeviceAttributeUpdatePermissionRequest() 396 elif request_type == 'device_attribute_update': 397 response = self.ProcessDeviceAttributeUpdateRequest() 398 elif request_type == 'remote_commands': 399 response = self.ProcessRemoteCommandsRequest() 400 elif request_type == 'check_android_management': 401 response = self.ProcessCheckAndroidManagementRequest( 402 rmsg.check_android_management_request, 403 str(self.GetUniqueParam('oauth_token'))) 404 elif request_type == 'register_browser': 405 response = self.ProcessRegisterBrowserRequest( 406 rmsg.register_browser_request) 407 elif request_type == 'chrome_desktop_report': 408 response = self.ProcessChromeDesktopReportUploadRequest( 409 rmsg.chrome_desktop_report_request) 410 elif request_type == 'app_install_report': 411 response = self.ProcessAppInstallReportRequest( 412 rmsg.app_install_report_request) 413 elif request_type == 'client_cert_provisioning': 414 response = self.ProcessClientCertProvisioningRequest( 415 rmsg.client_certificate_provisioning_request) 416 else: 417 return (400, 'Invalid request parameter') 418 419 if isinstance(response[1], str): 420 body = response[1] 421 elif isinstance(response[1], google.protobuf.message.Message): 422 self.DumpMessage('Response', response[1]) 423 body = response[1].SerializeToString() 424 else: 425 body = '' 426 return (response[0], body) 427 428 def CreatePolicyForExternalPolicyData(self, policy_key): 429 """Returns an ExternalPolicyData protobuf for policy_key. 430 431 If there is policy data for policy_key then the download url will be 432 set so that it points to that data, and the appropriate hash is also set. 433 Otherwise, the protobuf will be empty. 434 435 Args: 436 policy_key: The policy type and settings entity id, joined by '/'. 437 438 Returns: 439 A serialized ExternalPolicyData. 440 """ 441 settings = ep.ExternalPolicyData() 442 data = self.server.ReadPolicyDataFromDataDir(policy_key) 443 if data: 444 settings.download_url = urlparse.urljoin( 445 self.server.GetBaseURL(), 'externalpolicydata?key=%s' % policy_key) 446 settings.secure_hash = hashlib.sha256(data).digest() 447 return settings.SerializeToString() 448 else: 449 return None 450 451 def CheckGoogleLogin(self): 452 """Extracts the auth token from the request and returns it. The token may 453 either be a GoogleLogin token from an Authorization header, or an OAuth V2 454 token from the oauth_token query parameter. Returns None if no token is 455 present. 456 """ 457 oauth_token = self.GetUniqueParam('oauth_token') 458 if oauth_token: 459 return oauth_token 460 461 match = re.match('GoogleLogin auth=(\\w+)', 462 self.headers.get('Authorization', '')) 463 if match: 464 return match.group(1) 465 466 return None 467 468 def CheckEnrollmentToken(self): 469 """Extracts the enrollment token from the request and returns it. The token 470 is GoogleEnrollmentToken token from an Authorization header. Returns None 471 if no token is present. 472 """ 473 match = re.match('GoogleEnrollmentToken token=(\\S+)', 474 self.headers.get('Authorization', '')) 475 if match: 476 return match.group(1) 477 478 return None 479 480 def ProcessRegister(self, msg): 481 """Handles a register request. 482 483 Checks the query for authorization and device identifier, registers the 484 device with the server and constructs a response. 485 486 Args: 487 msg: The DeviceRegisterRequest message received from the client. 488 489 Returns: 490 A tuple of HTTP status code and response data to send to the client. 491 """ 492 policy = self.server.GetPolicies() 493 # Check the auth token and device ID. 494 auth = self.CheckGoogleLogin() 495 if not auth: 496 return (403, 'No authorization') 497 498 if ('managed_users' not in policy): 499 return (500, 'Error in config - no managed users') 500 username = self.server.ResolveUser(auth) 501 if ('*' not in policy['managed_users'] and 502 username not in policy['managed_users']): 503 return (403, 'Unmanaged') 504 505 return self.RegisterDeviceAndSendResponse(msg, username) 506 507 def ProcessCertBasedRegister(self, signed_msg): 508 """Handles a certificate based register request. 509 510 Checks the query for the cert and device identifier, registers the 511 device with the server and constructs a response. 512 513 Args: 514 msg: The CertificateBasedDeviceRegisterRequest message received from 515 the client. 516 517 Returns: 518 A tuple of HTTP status code and response data to send to the client. 519 """ 520 # Unwrap the request 521 try: 522 req = self.UnwrapCertificateBasedDeviceRegistrationData( 523 signed_msg.signed_request) 524 except (IOError): 525 return(400, 'Invalid request') 526 527 # TODO(drcrash): Check the certificate itself. 528 if req.certificate_type != dm.CertificateBasedDeviceRegistrationData.\ 529 ENTERPRISE_ENROLLMENT_CERTIFICATE: 530 return(403, 'Invalid certificate type for registration') 531 532 register_req = req.device_register_request 533 username = None 534 535 if (register_req.flavor == dm.DeviceRegisterRequest. 536 FLAVOR_ENROLLMENT_ATTESTATION_USB_ENROLLMENT): 537 enrollment_token = self.CheckEnrollmentToken() 538 policy = self.server.GetPolicies() 539 if not enrollment_token: 540 return (401, 'Missing enrollment token.') 541 542 if ((not policy['token_enrollment']) or 543 (not policy['token_enrollment']['token']) or 544 (not policy['token_enrollment']['username'])): 545 return (500, 'Error in config - no token-based enrollment') 546 if policy['token_enrollment']['token'] != enrollment_token: 547 return (403, 'Invalid enrollment token') 548 username = policy['token_enrollment']['username'] 549 550 return self.RegisterDeviceAndSendResponse(register_req, username) 551 552 def RegisterDeviceAndSendResponse(self, msg, username): 553 """Registers a device and send a response to the client. 554 555 Checks that a device identifier was sent, registers the device 556 with the server and constructs a response. 557 """ 558 device_id = self.GetUniqueParam('deviceid') 559 if not device_id: 560 return (400, 'Missing device identifier') 561 562 token_info = self.server.RegisterDevice( 563 device_id, msg.machine_id, msg.type, username) 564 565 # Send back the reply. 566 response = dm.DeviceManagementResponse() 567 response.register_response.device_management_token = ( 568 token_info['device_token']) 569 response.register_response.machine_name = token_info['machine_name'] 570 response.register_response.enrollment_type = token_info['enrollment_mode'] 571 572 return (200, response) 573 574 def UnwrapCertificateBasedDeviceRegistrationData(self, msg): 575 """Verifies the signature of |msg| and if it is valid, return the 576 certificate based device registration data. If not, throws an 577 exception. 578 579 Args: 580 msg: SignedData received from the client. 581 582 Returns: 583 CertificateBasedDeviceRegistrationData 584 """ 585 # TODO(drcrash): Verify signature. 586 rdata = dm.CertificateBasedDeviceRegistrationData() 587 rdata.ParseFromString(msg.data[:len(msg.data) - msg.extra_data_bytes]) 588 return rdata 589 590 def ProcessApiAuthorization(self, msg): 591 """Handles an API authorization request. 592 593 Args: 594 msg: The DeviceServiceApiAccessRequest message received from the client. 595 596 Returns: 597 A tuple of HTTP status code and response data to send to the client. 598 """ 599 policy = self.server.GetPolicies() 600 601 # Return the auth code from the config file if it's defined. Default to an 602 # empty auth code, which will instruct the enrollment flow to skip robot 603 # auth setup. 604 response = dm.DeviceManagementResponse() 605 response.service_api_access_response.auth_code = policy.get( 606 'robot_api_auth_code', '') 607 608 return (200, response) 609 610 def ProcessUnregister(self, msg): 611 """Handles a register request. 612 613 Checks for authorization, unregisters the device and constructs the 614 response. 615 616 Args: 617 msg: The DeviceUnregisterRequest message received from the client. 618 619 Returns: 620 A tuple of HTTP status code and response data to send to the client. 621 """ 622 # Check the management token. 623 token, response = self.CheckToken() 624 if not token: 625 return response 626 627 # Unregister the device. 628 self.server.UnregisterDevice(token['device_token']) 629 630 # Prepare and send the response. 631 response = dm.DeviceManagementResponse() 632 response.unregister_response.CopyFrom(dm.DeviceUnregisterResponse()) 633 634 return (200, response) 635 636 def ProcessPolicy(self, msg, request_type): 637 """Handles a policy request. 638 639 Checks for authorization, encodes the policy into protobuf representation 640 and constructs the response. 641 642 Args: 643 msg: The DeviceManagementRequest message received from the client. 644 645 Returns: 646 A tuple of HTTP status code and response data to send to the client. 647 """ 648 token_info, error = self.CheckToken() 649 if not token_info: 650 return error 651 652 key_update_request = msg.device_state_key_update_request 653 if len(key_update_request.server_backed_state_keys) > 0: 654 self.server.UpdateStateKeys(token_info['device_token'], 655 key_update_request.server_backed_state_keys) 656 657 # See whether the |username| for the client is known. During policy 658 # validation, the client verifies that the policy blob is bound to the 659 # appropriate user by comparing against this value. In case the server is 660 # configured to resolve the actual user name from the access token via the 661 # token info endpoint, the resolved |username| has been stored in 662 # |token_info| when the client registered. If not, pass None as the 663 # |username| in which case a value from the configuration file will be used. 664 username = token_info.get('username') 665 666 # If this is a |publicaccount| request, use the |settings_entity_id| from 667 # the request as the |username|. This is required to validate policy for 668 # extensions in device-local accounts. 669 for request in msg.policy_request.requests: 670 if request.policy_type == 'google/chromeos/publicaccount': 671 username = request.settings_entity_id 672 673 response = dm.DeviceManagementResponse() 674 for request in msg.policy_request.requests: 675 if (request.policy_type in 676 ('google/android/user', 677 'google/chromeos/device', 678 'google/chromeos/publicaccount', 679 'google/chromeos/user', 680 'google/chrome/user', 681 'google/chrome/machine-level-user')): 682 fetch_response = response.policy_response.responses.add() 683 self.ProcessCloudPolicy(request, token_info, fetch_response, username) 684 elif (request.policy_type in 685 ('google/chrome/extension', 686 'google/chromeos/signinextension', 687 'google/chrome/machine-level-extension')): 688 self.ProcessCloudPolicyForExtensions( 689 request, response.policy_response, token_info, username) 690 else: 691 fetch_response.error_code = 400 692 fetch_response.error_message = 'Invalid policy_type' 693 694 return (200, response) 695 696 def ProcessAutoEnrollment(self, msg): 697 """Handles an auto-enrollment check request. 698 699 The reply depends on the value of the modulus: 700 1: replies with no new modulus and corresponding sha256 hashes. 701 2: replies with a new modulus, 4. 702 4: replies with a new modulus, 2. 703 8: fails with error 400. 704 16: replies with a new modulus, 16. 705 32: replies with a new modulus, 1. 706 anything else: replies with no new modulus and an empty list of hashes 707 708 These allow the client to pick the testing scenario its wants to simulate. 709 710 Args: 711 msg: The DeviceAutoEnrollmentRequest message received from the client. 712 713 Returns: 714 A tuple of HTTP status code and response data to send to the client. 715 """ 716 auto_enrollment_response = dm.DeviceAutoEnrollmentResponse() 717 718 if msg.modulus == 1: 719 if (msg.enrollment_check_type == dm.DeviceAutoEnrollmentRequest. 720 ENROLLMENT_CHECK_TYPE_FRE): 721 auto_enrollment_response.hashes.extend( 722 self.server.GetMatchingStateKeyHashes(msg.modulus, msg.remainder)) 723 elif (msg.enrollment_check_type == dm.DeviceAutoEnrollmentRequest. 724 ENROLLMENT_CHECK_TYPE_FORCED_ENROLLMENT): 725 auto_enrollment_response.hashes.extend( 726 self.server.GetMatchingSerialHashes(msg.modulus, msg.remainder)) 727 elif msg.modulus == 2: 728 auto_enrollment_response.expected_modulus = 4 729 elif msg.modulus == 4: 730 auto_enrollment_response.expected_modulus = 2 731 elif msg.modulus == 8: 732 return (400, 'Server error') 733 elif msg.modulus == 16: 734 auto_enrollment_response.expected_modulus = 16 735 elif msg.modulus == 32: 736 auto_enrollment_response.expected_modulus = 1 737 738 response = dm.DeviceManagementResponse() 739 response.auto_enrollment_response.CopyFrom(auto_enrollment_response) 740 return (200, response) 741 742 def ProcessDeviceInitialEnrollmentState(self, msg): 743 """Handles a device initial enrollment state request. 744 745 Response data is taken from server configuration. 746 747 Returns: 748 A tuple of HTTP status code and response data to send to the client. 749 """ 750 device_initial_enrollment_state_response = ( 751 dm.DeviceInitialEnrollmentStateResponse()) 752 753 brand_serial_id = msg.brand_code + '_' + msg.serial_number; 754 initial_state_dict = (self.server.GetPolicies(). 755 get('initial_enrollment_state', {})) 756 state = initial_state_dict.get(brand_serial_id, {}) 757 758 FIELDS = [ 759 'initial_enrollment_mode', 760 'management_domain', 761 'is_license_packaged_with_device', 762 ] 763 for field in FIELDS: 764 if field in state: 765 setattr(device_initial_enrollment_state_response, field, state[field]) 766 767 response = dm.DeviceManagementResponse() 768 response.device_initial_enrollment_state_response.CopyFrom( 769 device_initial_enrollment_state_response) 770 return (200, response) 771 772 def ProcessDeviceStateRetrievalRequest(self, msg): 773 """Handles a device state retrieval request. 774 775 Response data is taken from server configuration. 776 777 Returns: 778 A tuple of HTTP status code and response data to send to the client. 779 """ 780 device_state_retrieval_response = dm.DeviceStateRetrievalResponse() 781 782 client = self.server.LookupByStateKey(msg.server_backed_state_key) 783 if client is not None: 784 state = self.server.GetPolicies().get('device_state', {}) 785 FIELDS = [ 786 'management_domain', 787 'restore_mode', 788 ] 789 for field in FIELDS: 790 if field in state: 791 setattr(device_state_retrieval_response, field, state[field]) 792 793 response = dm.DeviceManagementResponse() 794 response.device_state_retrieval_response.CopyFrom( 795 device_state_retrieval_response) 796 return (200, response) 797 798 def ProcessStatusUploadRequest(self, device_status, session_status): 799 """Handles a device/session status upload request. 800 801 Returns: 802 A tuple of HTTP status code and response data to send to the client. 803 """ 804 # Empty responses indicate a successful upload. 805 device_status_report_response = dm.DeviceStatusReportResponse() 806 session_status_report_response = dm.SessionStatusReportResponse() 807 808 response = dm.DeviceManagementResponse() 809 response.device_status_report_response.CopyFrom( 810 device_status_report_response) 811 response.session_status_report_response.CopyFrom( 812 session_status_report_response) 813 814 return (200, response) 815 816 def ProcessDeviceAttributeUpdatePermissionRequest(self): 817 """Handles a device attribute update permission request. 818 819 Returns: 820 A tuple of HTTP status code and response data to send to the client. 821 """ 822 response = dm.DeviceManagementResponse() 823 policy = self.server.GetPolicies() 824 update_allowed = True 825 if ('allow_set_device_attributes' in policy): 826 update_allowed = policy['allow_set_device_attributes'] 827 828 response.device_attribute_update_permission_response.result = ( 829 dm.DeviceAttributeUpdatePermissionResponse.ATTRIBUTE_UPDATE_ALLOWED 830 if update_allowed else 831 dm.DeviceAttributeUpdatePermissionResponse.ATTRIBUTE_UPDATE_DISALLOWED) 832 833 return (200, response) 834 835 def ProcessDeviceAttributeUpdateRequest(self): 836 """Handles a device attribute update request. 837 838 Returns: 839 A tuple of HTTP status code and response data to send to the client. 840 """ 841 response = dm.DeviceManagementResponse() 842 response.device_attribute_update_response.result = ( 843 dm.DeviceAttributeUpdateResponse.ATTRIBUTE_UPDATE_SUCCESS) 844 845 return (200, response) 846 847 def ProcessRemoteCommandsRequest(self): 848 """Handles a remote command request. 849 850 Returns: 851 A tuple of HTTP status code and response data to send to the client. 852 """ 853 return (200, '') 854 855 def ProcessCheckAndroidManagementRequest(self, msg, oauth_token): 856 """Handles a check Android management request. 857 858 Returns: 859 A tuple of HTTP status code and response data to send to the client. 860 """ 861 check_android_management_response = dm.CheckAndroidManagementResponse() 862 863 response = dm.DeviceManagementResponse() 864 response.check_android_management_response.CopyFrom( 865 check_android_management_response) 866 if oauth_token == 'managed-auth-token': 867 return (409, response) 868 elif oauth_token == 'unmanaged-auth-token': 869 return (200, response) 870 else: 871 return (403, response) 872 873 def ProcessAppInstallReportRequest(self, app_install_report): 874 """Handles a push-installed app report upload request. 875 876 Returns: 877 A tuple of HTTP status code and response data to send to the client. 878 """ 879 app_install_report_response = dm.AppInstallReportResponse() 880 response = dm.DeviceManagementResponse() 881 response.app_install_report_response.CopyFrom(app_install_report_response) 882 883 return (200, response) 884 885 def ProcessRegisterBrowserRequest(self, msg): 886 """Handles a browser registration request. 887 888 Returns: 889 A tuple of HTTP status code and response data to send to the client. 890 """ 891 enrollment_token = None 892 match = re.match('GoogleEnrollmentToken token=(\\w+)', 893 self.headers.get('Authorization', '')) 894 if match: 895 enrollment_token = match.group(1) 896 if not enrollment_token: 897 return (401, 'Missing enrollment token.') 898 899 device_id = self.GetUniqueParam('deviceid') 900 if not device_id: 901 return (400, 'Parameter deviceid is missing.') 902 903 if not msg.machine_name: 904 return (400, 'Invalid machine name: ') 905 906 if enrollment_token == INVALID_ENROLLMENT_TOKEN: 907 return (401, 'Invalid enrollment token') 908 909 dm_token = 'fake_device_management_token' 910 response = dm.DeviceManagementResponse() 911 response.register_response.device_management_token = ( 912 dm_token) 913 self.server.RegisterBrowser(dm_token, device_id, msg.machine_name) 914 915 return (200, response) 916 917 def ProcessChromeDesktopReportUploadRequest(self, chrome_desktop_report): 918 """Handles a chrome desktop report upload request. 919 920 Returns: 921 A tuple of HTTP status code and response data to send to the client. 922 """ 923 # Empty responses indicate a successful upload. 924 chrome_desktop_report_response = dm.ChromeDesktopReportResponse() 925 926 response = dm.DeviceManagementResponse() 927 response.chrome_desktop_report_response.CopyFrom( 928 chrome_desktop_report_response) 929 930 return (200, response) 931 932 def SetProtoRepeatedField(self, group_message, field, field_value): 933 assert type(field_value) == list 934 entries = group_message.__getattribute__(field.name) 935 if field.message_type is None: 936 for list_item in field_value: 937 entries.append(list_item) 938 else: 939 # This field is itself a protobuf. 940 sub_type = field.message_type 941 for sub_value in field_value: 942 assert type(sub_value) == dict 943 # Add a new sub-protobuf per list entry. 944 sub_message = entries.add() 945 # Now iterate over its fields and recursively add them. 946 for sub_field in sub_message.DESCRIPTOR.fields: 947 if sub_field.name in sub_value: 948 sub_field_value = sub_value[sub_field.name] 949 self.SetProtobufMessageField(sub_message, 950 sub_field, sub_field_value) 951 952 def SetProtoMessageField(self, group_message, field, field_value): 953 if field.message_type.name == 'StringList': 954 assert type(field_value) == list 955 entries = group_message.__getattribute__(field.name).entries 956 for list_item in field_value: 957 entries.append(list_item) 958 else: 959 assert type(field_value) == dict 960 sub_message = group_message.__getattribute__(field.name) 961 for sub_field in sub_message.DESCRIPTOR.fields: 962 if sub_field.name in field_value: 963 sub_field_value = field_value[sub_field.name] 964 self.SetProtobufMessageField(sub_message, sub_field, sub_field_value) 965 966 def SetProtoField(self, group_message, field, field_value): 967 if field.type == field.TYPE_BOOL: 968 assert type(field_value) == bool 969 elif field.type == field.TYPE_STRING: 970 if six.PY3: 971 assert isinstance(field_value, str) 972 else: 973 assert type(field_value) in [str, unicode] 974 elif field.type == field.TYPE_BYTES: 975 if six.PY3: 976 assert isinstance(field_value, str) 977 else: 978 assert type(field_value) in [str, unicode] 979 field_value = field_value.decode('hex') 980 elif (field.type == field.TYPE_INT64 or 981 field.type == field.TYPE_INT32 or 982 field.type == field.TYPE_ENUM): 983 assert type(field_value) == int 984 else: 985 return False 986 setattr(group_message, field.name, field_value) 987 return True 988 989 def SetProtobufMessageField(self, group_message, field, field_value): 990 """Sets a field in a protobuf message. 991 992 Args: 993 group_message: The protobuf message. 994 field: The field of the message to set, it should be a member of 995 group_message.DESCRIPTOR.fields. 996 field_value: The value to set. 997 """ 998 if field.label == field.LABEL_REPEATED: 999 self.SetProtoRepeatedField(group_message, field, field_value) 1000 elif field.type == field.TYPE_MESSAGE: 1001 self.SetProtoMessageField(group_message, field, field_value) 1002 elif not self.SetProtoField(group_message, field, field_value): 1003 raise Exception('Unknown field type %s' % field.type) 1004 1005 def GatherExtensionPolicySettings(self, settings, policies): 1006 """Copies all the policies from a dictionary into a protobuf of type 1007 ExternalPolicyData. 1008 1009 Args: 1010 settings: The destination: a ExternalPolicyData protobuf. 1011 policies: The source: a dictionary containing the extension policies. 1012 """ 1013 for field in settings.DESCRIPTOR.fields: 1014 # |field| is the entry for a specific policy in the top-level 1015 # ExternalPolicyData proto. 1016 field_value = policies.get(field.name) 1017 if field_value is None: 1018 continue 1019 1020 field_descriptor = settings.DESCRIPTOR.fields_by_name[field.name] 1021 self.SetProtobufMessageField(settings, field_descriptor, 1022 field_value) 1023 1024 def GetMessageDefinitionSource(self, message_type): 1025 """Retrieve either policy_common_defintions, or chrome_device_policy 1026 proto file, which contains the definition of the message. 1027 1028 Args: 1029 message_type: name of the message definition type. 1030 """ 1031 if message_type in POLICY_COMMON_DEFINITIONS_TYPES: 1032 return 'cd' 1033 return 'dp' 1034 1035 def GatherDevicePolicySettings(self, settings, policies): 1036 """Copies all the policies from a dictionary into a protobuf of type 1037 CloudDeviceSettingsProto. 1038 1039 Args: 1040 settings: The destination ChromeDeviceSettingsProto protobuf. 1041 policies: The source dictionary containing policies in JSON format. 1042 """ 1043 for group in settings.DESCRIPTOR.fields: 1044 # Create protobuf message for group. 1045 group_message = eval(self.GetMessageDefinitionSource( 1046 group.message_type.name) + '.' + group.message_type.name + '()') 1047 # Indicates if at least one field was set in |group_message|. 1048 got_fields = False 1049 # Iterate over fields of the message and feed them from the 1050 # policy config file. 1051 for field in group_message.DESCRIPTOR.fields: 1052 field_value = None 1053 full_name = '{}.{}'.format(group.name, field.name) 1054 if full_name in policies: 1055 got_fields = True 1056 field_value = policies[full_name] 1057 self.SetProtobufMessageField(group_message, field, field_value) 1058 elif field.name in policies: 1059 got_fields = True 1060 field_value = policies[field.name] 1061 self.SetProtobufMessageField(group_message, field, field_value) 1062 if got_fields: 1063 settings.__getattribute__(group.name).CopyFrom(group_message) 1064 1065 def GatherUserPolicySettings(self, settings, policies): 1066 """Copies all the policies from a dictionary into a protobuf of type 1067 CloudPolicySettings. 1068 1069 Args: 1070 settings: The destination: a CloudPolicySettings protobuf. 1071 policies: The source: a dictionary containing policies under keys 1072 'recommended' and 'mandatory'. 1073 """ 1074 for field in settings.DESCRIPTOR.fields: 1075 # |field| is the entry for a specific policy in the top-level 1076 # CloudPolicySettings proto. 1077 1078 # Look for this policy's value in the mandatory or recommended dicts. 1079 if field.name in policies.get('mandatory', {}): 1080 mode = cd.PolicyOptions.MANDATORY 1081 value = policies['mandatory'][field.name] 1082 elif field.name in policies.get('recommended', {}): 1083 mode = cd.PolicyOptions.RECOMMENDED 1084 value = policies['recommended'][field.name] 1085 else: 1086 continue 1087 1088 # Create protobuf message for this policy. 1089 policy_message = eval('cd.' + field.message_type.name + '()') 1090 policy_message.policy_options.mode = mode 1091 field_descriptor = policy_message.DESCRIPTOR.fields_by_name['value'] 1092 self.SetProtobufMessageField(policy_message, field_descriptor, value) 1093 settings.__getattribute__(field.name).CopyFrom(policy_message) 1094 1095 def ProcessCloudPolicyForExtensions(self, request, response, token_info, 1096 username=None): 1097 """Handles a request for policy for extensions. 1098 1099 A request for policy for extensions is slightly different from the other 1100 cloud policy requests, because it can trigger 0, one or many 1101 PolicyFetchResponse messages in the response. 1102 1103 Args: 1104 request: The PolicyFetchRequest that triggered this handler. 1105 response: The DevicePolicyResponse message for the response. Multiple 1106 PolicyFetchResponses will be appended to this message. 1107 token_info: The token extracted from the request. 1108 username: The username for the response. May be None. 1109 """ 1110 # Send one PolicyFetchResponse for each extension that has 1111 # configuration data at the server. 1112 ids = self.server.ListMatchingComponents(request.policy_type) 1113 if not ids: 1114 # Fetch the ids from the policy JSON, if none in the config directory. 1115 policy = self.server.GetPolicies() 1116 ext_policies = policy.get(request.policy_type, {}) 1117 ids = list(ext_policies.keys()) 1118 1119 for settings_entity_id in ids: 1120 # Reuse the extension policy request, to trigger the same signature 1121 # type in the response. 1122 request.settings_entity_id = settings_entity_id 1123 fetch_response = response.responses.add() 1124 self.ProcessCloudPolicy(request, token_info, fetch_response, username) 1125 # Don't do key rotations for these messages. 1126 fetch_response.ClearField('new_public_key') 1127 fetch_response.ClearField('new_public_key_signature') 1128 fetch_response.ClearField( 1129 'new_public_key_verification_signature_deprecated') 1130 1131 def ProcessCloudPolicy(self, msg, token_info, response, username=None): 1132 """Handles a cloud policy request. (New protocol for policy requests.) 1133 1134 Encodes the policy into protobuf representation, signs it and constructs 1135 the response. 1136 1137 Args: 1138 msg: The CloudPolicyRequest message received from the client. 1139 token_info: The token extracted from the request. 1140 response: A PolicyFetchResponse message that should be filled with the 1141 response data. 1142 username: The username for the response. May be None. 1143 """ 1144 1145 # Response is only given if the scope is specified in the config file. 1146 # Normally 'google/chromeos/device', 'google/chromeos/user' and 1147 # 'google/chromeos/publicaccount' should be accepted. 1148 policy = self.server.GetPolicies() 1149 policy_value = '' 1150 policy_key = msg.policy_type 1151 if msg.settings_entity_id: 1152 policy_key += '/' + msg.settings_entity_id 1153 if msg.policy_type in token_info['allowed_policy_types']: 1154 if msg.policy_type in ('google/android/user', 1155 'google/chromeos/publicaccount', 1156 'google/chromeos/user', 1157 'google/chrome/user', 1158 'google/chrome/machine-level-user'): 1159 settings = cp.CloudPolicySettings() 1160 payload = self.server.ReadPolicyFromDataDir(policy_key, settings) 1161 if payload is None: 1162 self.GatherUserPolicySettings(settings, policy.get(policy_key, {})) 1163 payload = settings.SerializeToString() 1164 elif msg.policy_type == 'google/chromeos/device': 1165 settings = dp.ChromeDeviceSettingsProto() 1166 payload = self.server.ReadPolicyFromDataDir(policy_key, settings) 1167 if payload is None: 1168 self.GatherDevicePolicySettings(settings, policy.get(policy_key, {})) 1169 payload = settings.SerializeToString() 1170 elif msg.policy_type in ('google/chrome/extension', 1171 'google/chromeos/signinextension', 1172 'google/chrome/machine-level-extension'): 1173 settings = ep.ExternalPolicyData() 1174 payload = self.server.ReadPolicyFromDataDir(policy_key, settings) 1175 if payload is None: 1176 payload = self.CreatePolicyForExternalPolicyData(policy_key) 1177 if payload is None: 1178 ext_policies = policy.get(msg.policy_type, {}) 1179 policies = ext_policies.get(msg.settings_entity_id, {}) 1180 self.GatherExtensionPolicySettings(settings, policies) 1181 payload = settings.SerializeToString() 1182 else: 1183 response.error_code = 400 1184 response.error_message = 'Invalid policy type' 1185 return 1186 else: 1187 response.error_code = 400 1188 response.error_message = 'Request not allowed for the token used' 1189 return 1190 1191 # Determine the current key on the client. 1192 client_key_version = None 1193 client_key = None 1194 if msg.HasField('public_key_version'): 1195 client_key_version = msg.public_key_version 1196 client_key = self.server.GetKeyByVersion(client_key_version) 1197 if client_key is None: 1198 response.error_code = 400 1199 response.error_message = 'Invalid public key version' 1200 return 1201 1202 # Choose the key for signing the policy. 1203 signing_key_version = self.server.GetKeyVersionForSigning( 1204 client_key_version) 1205 signing_key = self.server.GetKeyByVersion(signing_key_version) 1206 assert signing_key is not None 1207 1208 # Fill the policy data protobuf. 1209 policy_data = dm.PolicyData() 1210 policy_data.policy_type = msg.policy_type 1211 policy_data.timestamp = int(time.time() * 1000) 1212 policy_data.request_token = token_info['device_token'] 1213 policy_data.policy_value = payload 1214 policy_data.machine_name = token_info['machine_name'] 1215 policy_data.settings_entity_id = msg.settings_entity_id 1216 policy_data.service_account_identity = policy.get( 1217 'service_account_identity', 1218 'policy_testserver.py-service_account_identity@gmail.com') 1219 1220 policy_invalidation_topic = policy.get('policy_invalidation_topic') 1221 if policy_invalidation_topic is not None: 1222 policy_data.policy_invalidation_topic = \ 1223 policy_invalidation_topic.encode('ascii') 1224 1225 if msg.signature_type != dm.PolicyFetchRequest.NONE: 1226 policy_data.public_key_version = signing_key_version 1227 1228 if username: 1229 policy_data.username = username 1230 else: 1231 # If the correct |username| is unknown, rely on a manually-configured 1232 # username from the configuration file or use a default. 1233 policy_data.username = policy.get('policy_user', 'username@example.com') 1234 policy_data.device_id = token_info['device_id'] 1235 1236 # Set affiliation IDs so that user was managed on the device. 1237 device_affiliation_ids = policy.get('device_affiliation_ids') 1238 if device_affiliation_ids: 1239 policy_data.device_affiliation_ids.extend(device_affiliation_ids) 1240 1241 user_affiliation_ids = policy.get('user_affiliation_ids') 1242 if user_affiliation_ids: 1243 policy_data.user_affiliation_ids.extend(user_affiliation_ids) 1244 1245 if msg.policy_type == 'google/chromeos/device': 1246 # Fill |obfuscated_customer_id| for PolicyData in device policy fetches. 1247 # Verified Access attestation using the Enterprise Machine Key (EMK) 1248 # requires it since https://crbug.com/1073974. 1249 policy_data.obfuscated_customer_id = OBFUSCATED_CUSTOMER_ID 1250 1251 response.policy_data = policy_data.SerializeToString() 1252 1253 # Sign the serialized policy data 1254 if msg.signature_type == dm.PolicyFetchRequest.SHA1_RSA: 1255 response.policy_data_signature = bytes( 1256 signing_key['private_key'].hashAndSign(response.policy_data)) 1257 if msg.public_key_version != signing_key_version: 1258 response.new_public_key = signing_key['public_key'] 1259 1260 # Set the verification signature appropriate for the policy domain. 1261 # TODO(atwilson): Use the enrollment domain for public accounts when 1262 # we add key validation for ChromeOS (http://crbug.com/328038). 1263 if 'signatures' in signing_key: 1264 verification_sig = self.GetSignatureForDomain( 1265 signing_key['signatures'], policy_data.username) 1266 1267 if verification_sig: 1268 assert len(verification_sig) == 256, \ 1269 'bad signature size: %d' % len(verification_sig) 1270 response.new_public_key_verification_signature_deprecated = ( 1271 verification_sig) 1272 1273 if client_key is not None: 1274 response.new_public_key_signature = bytes( 1275 client_key['private_key'].hashAndSign(response.new_public_key)) 1276 1277 return (200, response.SerializeToString()) 1278 1279 def ProcessClientCertProvisioningRequest(self, msg): 1280 """Handles a client certificate provisioning request. 1281 1282 Issues a client certificate generated with the public key provided and 1283 sends it back to the requesting client. 1284 1285 Args: 1286 msg: The ClientCertificateProvisioningRequest message received from the 1287 client. Contains one of start_csr_request, finish_csr_request, or 1288 download_cert_request. 1289 1290 Returns: 1291 A tuple of HTTP status code and a ClientCertificateProvisioningResponse 1292 message that is filled with hard coded data and in the case of a 1293 download_cert_request in addition with the generated certificate. 1294 """ 1295 1296 if crypto == None: 1297 return (400, 'Could not find pyopenssl.') 1298 1299 if msg.HasField('start_csr_request'): 1300 start_csr_response = dm.StartCsrResponse() 1301 1302 # real but outdated b64 encoded verified access challenge received from 1303 # the Enterprise Verified Access Test extension 1304 va_challenge_b64 = ( 1305 'CkEKFkVudGVycHJpc2VLZXlDaGFsbGVuZ2USIO6YSl1AvTjbEvRukIFMF2pA4AwCc1w4f' 1306 'ZX3n3sGcLInGOPh+IWKLhKAAm/WHGk7ahCjPk4IXLfDlUUmmZdfW1scNcwkKk/x24Znvb' 1307 'T7tyrmxLzO5nG69ycW7f+2bacbtfGlf0UOGeljcqBIIoHjJPlm0d2gCTa2msghS9ovaSg' 1308 '/wbY5DPeNkcG5drDq5Es5hzlZ49Bhvv5cAbDDsGNobPJQ3ojbu/mrdlb3mlB1oNTmbfoP' 1309 'TBrr6n9JXvywsJmHyInTySiFPOR8TT1cQoDA0pZ0ccHMJfLia1/FCW/pGpI6GpSzCQLq2' 1310 'hH0cFVuef3lGn09EeUHTPejbm6gcrHe9VDAFXMI8SzUlgMBBjHtTpo9GXJbwkTrGFXdkE' 1311 'U5BY1KukrsIVqdmAGFTDM=' 1312 ) 1313 1314 va_challenge = base64.b64decode(va_challenge_b64) 1315 start_csr_response.invalidation_topic = 'invalidation_topic_123' 1316 start_csr_response.va_challenge = va_challenge 1317 start_csr_response.hashing_algorithm = 2 1318 start_csr_response.signing_algorithm = 1 1319 start_csr_response.data_to_sign = 'data_to_sign_123' 1320 1321 response = dm.DeviceManagementResponse() 1322 response.client_certificate_provisioning_response.start_csr_response.\ 1323 CopyFrom(start_csr_response) 1324 return (200, response) 1325 1326 if msg.HasField('finish_csr_request'): 1327 finish_csr_response = dm.FinishCsrResponse() 1328 1329 response = dm.DeviceManagementResponse() 1330 response.client_certificate_provisioning_response.finish_csr_response.\ 1331 CopyFrom(finish_csr_response) 1332 return (200, response) 1333 1334 if msg.HasField('download_cert_request'): 1335 download_cert_response = dm.DownloadCertResponse() 1336 1337 pubKey = crypto.load_publickey(crypto.FILETYPE_ASN1, msg.public_key) 1338 1339 caPrivKey = crypto.load_privatekey( 1340 crypto.FILETYPE_PEM, CERT_PROVISIONING_CA_PRIVATE_KEY_PEM, 'pass') 1341 1342 cert = crypto.X509() 1343 cert.set_serial_number(3) 1344 cert.gmtime_adj_notBefore(0) 1345 cert.gmtime_adj_notAfter(365*24*60*60) 1346 cert.get_subject().CN = "TastTest" 1347 cert.set_issuer(cert.get_subject()) 1348 cert.set_issuer(cert.get_subject()) 1349 cert.set_pubkey(pubKey) 1350 cert.sign(caPrivKey, 'sha256') 1351 1352 download_cert_response.pem_encoded_certificate =\ 1353 crypto.dump_certificate(crypto.FILETYPE_PEM, cert) 1354 1355 response = dm.DeviceManagementResponse() 1356 response.client_certificate_provisioning_response.\ 1357 download_cert_response.CopyFrom(download_cert_response) 1358 return (200, response) 1359 1360 return (400, 'Invalid request parameter') 1361 1362 def GetSignatureForDomain(self, signatures, username): 1363 parsed_username = username.split("@", 1) 1364 if len(parsed_username) != 2: 1365 logging.error('Could not extract domain from username: %s' % username) 1366 return None 1367 domain = parsed_username[1] 1368 1369 # Lookup the domain's signature in the passed dictionary. If none is found, 1370 # fallback to a wildcard signature. 1371 if domain in signatures: 1372 return signatures[domain] 1373 if '*' in signatures: 1374 return signatures['*'] 1375 1376 # No key matching this domain. 1377 logging.error('No verification signature matching domain: %s' % domain) 1378 return None 1379 1380 def CheckToken(self): 1381 """Helper for checking whether the client supplied a valid DM token. 1382 1383 Extracts the token from the request and passed to the server in order to 1384 look up the client. 1385 1386 Returns: 1387 A pair of token information record and error response. If the first 1388 element is None, then the second contains an error code to send back to 1389 the client. Otherwise the first element is the same structure that is 1390 returned by LookupToken(). 1391 """ 1392 error = 500 1393 dmtoken = None 1394 request_device_id = self.GetUniqueParam('deviceid') 1395 match = re.match('GoogleDMToken token=(\\w+)', 1396 self.headers.get('Authorization', '')) 1397 if match: 1398 dmtoken = match.group(1) 1399 if not dmtoken: 1400 error = 401 1401 else: 1402 token_info = self.server.LookupToken(dmtoken) 1403 if (not token_info or 1404 not request_device_id or 1405 token_info['device_id'] != request_device_id): 1406 error = 410 1407 else: 1408 return (token_info, None) 1409 1410 logging.debug('Token check failed with error %d' % error) 1411 1412 return (None, (error, 'Server error %d' % error)) 1413 1414 def DumpMessage(self, label, msg): 1415 """Helper for logging an ASCII dump of a protobuf message.""" 1416 logging.debug('%s\n%s' % (label, str(msg))) 1417 1418 def GetExpectedError(self, request): 1419 """ 1420 Returns the preset HTTP error for |request| if it is defined in 1421 configuration. 1422 1423 Returns: 1424 A tuple of HTTP status code and response data to send to the client or 1425 None if no error was defined. 1426 """ 1427 policy = self.server.GetPolicies() 1428 if 'request_errors' in policy: 1429 errors = policy['request_errors'] 1430 if (request in errors) and (errors[request] > 0): 1431 return errors[request], 'Preconfigured error' 1432 return None 1433 1434class PolicyTestServer(testserver_base.BrokenPipeHandlerMixIn, 1435 testserver_base.StoppableHTTPServer): 1436 """Handles requests and keeps global service state.""" 1437 1438 def __init__(self, server_address, data_dir, policy_path, client_state_file, 1439 private_key_paths, rotate_keys_automatically, server_base_url): 1440 """Initializes the server. 1441 1442 Args: 1443 server_address: Server host and port. 1444 data_dir: Directory that contains files with signature, policy, clients 1445 information. 1446 policy_path: Names the file to read JSON-formatted policy from. 1447 client_state_file: Path to file with registered clients. 1448 private_key_paths: List of paths to read private keys from. 1449 rotate_keys_automatically: Whether the keys should be rotated in a 1450 round-robin fashion for each policy request (by default, either the 1451 key specified in the config or the first key will be used for all 1452 requests). 1453 server_base_url: The server base URL. Used for ExternalPolicyData message. 1454 """ 1455 testserver_base.StoppableHTTPServer.__init__(self, server_address, 1456 PolicyRequestHandler) 1457 self.data_dir = data_dir 1458 self.policy_path = policy_path 1459 self.rotate_keys_automatically = rotate_keys_automatically 1460 self.server_base_url = server_base_url 1461 1462 # _registered_tokens and client_state_file kept in sync if the file is set. 1463 self._registered_tokens = {} 1464 self.client_state_file = client_state_file 1465 self.client_state_file_timestamp = 0 1466 1467 self.keys = [] 1468 if private_key_paths: 1469 # Load specified keys from the filesystem. 1470 for key_path in private_key_paths: 1471 try: 1472 key_str = open(key_path).read() 1473 except IOError: 1474 print('Failed to load private key from %s' % key_path) 1475 continue 1476 try: 1477 key = tlslite.api.parsePEMKey(key_str, private=True) 1478 except SyntaxError: 1479 key = tlslite.utils.python_rsakey.Python_RSAKey._parsePKCS8( 1480 bytearray(key_str)) 1481 1482 assert key is not None 1483 key_info = { 'private_key' : key } 1484 1485 # Now try to read in a signature, if one exists. 1486 try: 1487 key_sig = open(key_path + '.sig').read() 1488 # Create a dictionary with the wildcard domain + signature 1489 key_info['signatures'] = {'*': key_sig} 1490 except IOError: 1491 print('Failed to read validation signature from %s.sig' % key_path) 1492 self.keys.append(key_info) 1493 else: 1494 # Use the canned private keys if none were passed from the command line. 1495 for signing_key in SIGNING_KEYS: 1496 decoded_key = base64.b64decode(signing_key['key']); 1497 key = tlslite.utils.python_rsakey.Python_RSAKey._parsePKCS8( 1498 bytearray(decoded_key)) 1499 assert key is not None 1500 # Grab the signature dictionary for this key and decode all of the 1501 # signatures. 1502 signature_dict = signing_key['signatures'] 1503 decoded_signatures = {} 1504 for domain in signature_dict: 1505 decoded_signatures[domain] = base64.b64decode(signature_dict[domain]) 1506 self.keys.append({'private_key': key, 1507 'signatures': decoded_signatures}) 1508 1509 # Derive the public keys from the private keys. 1510 for entry in self.keys: 1511 key = entry['private_key'] 1512 1513 algorithm = asn1der.Sequence( 1514 [ asn1der.Data(asn1der.OBJECT_IDENTIFIER, PKCS1_RSA_OID), 1515 asn1der.Data(asn1der.NULL, b'') ]) 1516 rsa_pubkey = asn1der.Sequence([ asn1der.Integer(key.n), 1517 asn1der.Integer(key.e) ]) 1518 pubkey = asn1der.Sequence([ algorithm, asn1der.Bitstring(rsa_pubkey) ]) 1519 entry['public_key'] = pubkey 1520 1521 try: 1522 self.ReadClientStateFile() 1523 except Exception as e: 1524 # Could fail if file is not written yet. 1525 logging.info('failed to load client state %s' % e) 1526 1527 def GetPolicies(self): 1528 """Returns the policies to be used, reloaded from the backend file every 1529 time this is called. 1530 """ 1531 policy = {} 1532 if json is None: 1533 logging.error('No JSON module, cannot parse policy information') 1534 elif not os.path.exists(self.policy_path): 1535 logging.warning('Missing policies file %s' % self.policy_path) 1536 else: 1537 try: 1538 policy = json.loads(open(self.policy_path).read(), strict=False) 1539 except IOError: 1540 logging.error('Failed to load policies from %s' % self.policy_path) 1541 return policy 1542 1543 def GetKeyByVersion(self, key_version): 1544 """Obtains the object containing key properties, given the key version. 1545 1546 Args: 1547 key_version: Integer key version. 1548 1549 Returns: 1550 The object containing key properties, or None if the key is not found. 1551 """ 1552 # Convert the policy key version, which has to be positive according to the 1553 # policy protocol definition, to an index in the keys list. 1554 key_index = key_version - 1 1555 if key_index < 0: 1556 return None 1557 if key_index >= len(self.keys): 1558 if self.rotate_keys_automatically: 1559 key_index %= len(self.keys) 1560 else: 1561 return None 1562 return self.keys[key_index] 1563 1564 def GetKeyVersionForSigning(self, client_key_version): 1565 """Determines the version of the key that should be used for signing policy. 1566 1567 Args: 1568 client_key_version: Either an integer representing the current key version 1569 provided by the client, or None if the client didn't provide any. 1570 1571 Returns: 1572 An integer representing the signing key version. 1573 """ 1574 if self.rotate_keys_automatically and client_key_version is not None: 1575 # Return the incremented version, which means that the key should be 1576 # rotated. 1577 return client_key_version + 1 1578 # Return the version that is specified by the config, defaulting to using 1579 # the very first key. Note that incrementing here is done due to conversion 1580 # between indices in the keys list and the key versions transmitted to the 1581 # client (where the latter have to be positive according to the policy 1582 # protocol definition). 1583 return self.GetPolicies().get('current_key_index', 0) + 1 1584 1585 def ResolveUser(self, auth_token): 1586 """Tries to resolve an auth token to the corresponding user name. 1587 1588 If enabled, this makes a request to the token info endpoint to determine the 1589 user ID corresponding to the token. If token resolution is disabled or the 1590 request fails, this will return the policy_user config parameter. 1591 """ 1592 config = self.GetPolicies() 1593 token_info_url = config.get('token_info_url') 1594 if token_info_url is not None: 1595 try: 1596 token_info = urllib_request.urlopen(token_info_url + '?' + 1597 urlparse.urlencode({'access_token': auth_token})).read() 1598 return json.loads(token_info)['email'] 1599 except Exception as e: 1600 logging.info('Failed to resolve user: %s', e) 1601 1602 return config.get('policy_user') 1603 1604 def RegisterDevice(self, device_id, machine_id, type, username): 1605 """Registers a device or user and generates a DM token for it. 1606 1607 Args: 1608 device_id: The device identifier provided by the client. 1609 1610 Returns: 1611 The newly generated device token for the device. 1612 """ 1613 dmtoken_chars = [] 1614 while len(dmtoken_chars) < 32: 1615 dmtoken_chars.append(random.choice('0123456789abcdef')) 1616 dmtoken = ''.join(dmtoken_chars) 1617 allowed_policy_types = { 1618 dm.DeviceRegisterRequest.BROWSER: [ 1619 'google/chrome/user', 1620 'google/chrome/extension' 1621 ], 1622 dm.DeviceRegisterRequest.USER: [ 1623 'google/chromeos/user', 1624 'google/chrome/extension' 1625 ], 1626 dm.DeviceRegisterRequest.DEVICE: [ 1627 'google/chromeos/device', 1628 'google/chromeos/publicaccount', 1629 'google/chrome/extension', 1630 'google/chromeos/signinextension' 1631 ], 1632 dm.DeviceRegisterRequest.ANDROID_BROWSER: [ 1633 'google/android/user' 1634 ], 1635 dm.DeviceRegisterRequest.TT: ['google/chromeos/user', 1636 'google/chrome/user'], 1637 } 1638 if machine_id in KIOSK_MACHINE_IDS: 1639 enrollment_mode = dm.DeviceRegisterResponse.RETAIL 1640 else: 1641 enrollment_mode = dm.DeviceRegisterResponse.ENTERPRISE 1642 self._registered_tokens[dmtoken] = { 1643 'device_id': device_id, 1644 'device_token': dmtoken, 1645 'allowed_policy_types': allowed_policy_types[type], 1646 'machine_name': 'chromeos-' + machine_id, 1647 'machine_id': machine_id, 1648 'enrollment_mode': enrollment_mode, 1649 'username': username, 1650 } 1651 self.WriteClientState() 1652 return self._registered_tokens[dmtoken] 1653 1654 def RegisterBrowser(self, dm_token, device_id, machine_name): 1655 self._registered_tokens[dm_token] = { 1656 'device_id': device_id, 1657 'device_token': dm_token, 1658 'allowed_policy_types': ['google/chrome/machine-level-user', 1659 'google/chrome/machine-level-extension'], 1660 'machine_name': machine_name 1661 } 1662 self.WriteClientState() 1663 1664 def UpdateStateKeys(self, dmtoken, state_keys): 1665 """Updates the state keys for a given client. 1666 1667 Args: 1668 dmtoken: The device management token provided by the client. 1669 state_keys: The state keys to set. 1670 """ 1671 if dmtoken in self._registered_tokens: 1672 self._registered_tokens[dmtoken]['state_keys'] = [key.encode('hex') 1673 for key in state_keys] 1674 self.WriteClientState() 1675 1676 def LookupToken(self, dmtoken): 1677 """Looks up a device or a user by DM token. 1678 1679 Args: 1680 dmtoken: The device management token provided by the client. 1681 1682 Returns: 1683 A dictionary with information about a device or user that is registered by 1684 dmtoken, or None if the token is not found. 1685 """ 1686 self.ReadClientStateFile() 1687 return self._registered_tokens.get(dmtoken, None) 1688 1689 def LookupByStateKey(self, state_key): 1690 """Looks up a device or a user by a state key. 1691 1692 Args: 1693 state_key: The state key provided by the client. 1694 1695 Returns: 1696 A dictionary with information about a device or user or None if there is 1697 no matching record. 1698 """ 1699 self.ReadClientStateFile() 1700 for client in list(self._registered_tokens.values()): 1701 if state_key.encode('hex') in client.get('state_keys', []): 1702 return client 1703 1704 return None 1705 1706 def GetMatchingStateKeyHashes(self, modulus, remainder): 1707 """Returns all clients registered with the server. 1708 1709 Returns: 1710 The list of registered clients. 1711 """ 1712 self.ReadClientStateFile() 1713 state_keys = sum([ c.get('state_keys', []) 1714 for c in list(self._registered_tokens.values()) ], []) 1715 hashed_keys = [hashlib.sha256(key.decode('hex')).digest() for key in 1716 set(state_keys)] 1717 return [hash for hash in hashed_keys if int(hash.encode('hex'), 16) 1718 % modulus == remainder] 1719 1720 def GetMatchingSerialHashes(self, modulus, remainder): 1721 """Returns all serial hashes from configuration. 1722 1723 Returns: 1724 The list of hashes 1725 """ 1726 brand_serial_keys = \ 1727 list(self.GetPolicies().get('initial_enrollment_state', {}).keys()) 1728 hashed_keys = [hashlib.sha256(key).digest()[0:8] for key in 1729 brand_serial_keys] 1730 return [hash for hash in hashed_keys if int(hash.encode('hex'), 16) 1731 % modulus == remainder] 1732 1733 1734 def UnregisterDevice(self, dmtoken): 1735 """Unregisters a device identified by the given DM token. 1736 1737 Args: 1738 dmtoken: The device management token provided by the client. 1739 """ 1740 if dmtoken in list(self._registered_tokens.keys()): 1741 del self._registered_tokens[dmtoken] 1742 self.WriteClientState() 1743 1744 def ReadClientStateFile(self): 1745 """ Loads _registered_tokens from client_state_file.""" 1746 if self.client_state_file is None: 1747 return 1748 file_timestamp = os.stat(self.client_state_file).st_mtime 1749 if file_timestamp == self.client_state_file_timestamp: 1750 return 1751 logging.info('load client state') 1752 file_contents = open(self.client_state_file).read() 1753 self._registered_tokens = json.loads(file_contents, strict=False) 1754 self.client_state_file_timestamp = file_timestamp 1755 1756 def WriteClientState(self): 1757 """Writes the client state back to the file.""" 1758 if self.client_state_file is not None: 1759 json_data = json.dumps(self._registered_tokens) 1760 open(self.client_state_file, 'w').write(json_data) 1761 self.client_state_file_timestamp = os.stat( 1762 self.client_state_file).st_mtime 1763 1764 def GetBaseFilename(self, policy_selector): 1765 """Returns the base filename for the given policy_selector. 1766 1767 Args: 1768 policy_selector: The policy type and settings entity id, joined by '/'. 1769 1770 Returns: 1771 The filename corresponding to the policy_selector, without a file 1772 extension. 1773 """ 1774 sanitized_policy_selector = re.sub('[^A-Za-z0-9.@-]', '_', policy_selector) 1775 return os.path.join(self.data_dir or '', 1776 'policy_%s' % sanitized_policy_selector) 1777 1778 def ListMatchingComponents(self, policy_type): 1779 """Returns a list of settings entity IDs that have a configuration file. 1780 1781 Args: 1782 policy_type: The policy type to look for. Only settings entity IDs for 1783 file selectors That match this policy_type will be returned. 1784 1785 Returns: 1786 A list of settings entity IDs for the given |policy_type| that have a 1787 configuration file in this server (either as a .bin, .txt or .data file). 1788 """ 1789 base_name = self.GetBaseFilename(policy_type) 1790 files = glob.glob('%s_*.*' % base_name) 1791 len_base_name = len(base_name) + 1 1792 return [ file[len_base_name:file.rfind('.')] for file in files ] 1793 1794 def ReadPolicyFromDataDir(self, policy_selector, proto_message): 1795 """Tries to read policy payload from a file in the data directory. 1796 1797 First checks for a binary rendition of the policy protobuf in 1798 <data_dir>/policy_<sanitized_policy_selector>.bin. If that exists, returns 1799 it. If that file doesn't exist, tries 1800 <data_dir>/policy_<sanitized_policy_selector>.txt and decodes that as a 1801 protobuf using proto_message. If that fails as well, returns None. 1802 1803 Args: 1804 policy_selector: Selects which policy to read. 1805 proto_message: Optional protobuf message object used for decoding the 1806 proto text format. 1807 1808 Returns: 1809 The binary payload message, or None if not found. 1810 """ 1811 base_filename = self.GetBaseFilename(policy_selector) 1812 1813 # Try the binary payload file first. 1814 try: 1815 return open(base_filename + '.bin').read() 1816 except IOError: 1817 pass 1818 1819 # If that fails, try the text version instead. 1820 if proto_message is None: 1821 return None 1822 1823 try: 1824 text = open(base_filename + '.txt').read() 1825 google.protobuf.text_format.Merge(text, proto_message) 1826 return proto_message.SerializeToString() 1827 except IOError: 1828 return None 1829 except google.protobuf.text_format.ParseError: 1830 return None 1831 1832 def ReadPolicyDataFromDataDir(self, policy_selector): 1833 """Returns the external policy data for |policy_selector| if found. 1834 1835 Args: 1836 policy_selector: Selects which policy to read. 1837 1838 Returns: 1839 The data for the corresponding policy type and entity id, if found. 1840 """ 1841 base_filename = self.GetBaseFilename(policy_selector) 1842 try: 1843 return open(base_filename + '.data').read() 1844 except IOError: 1845 return None 1846 1847 def GetBaseURL(self): 1848 """Returns the server base URL. 1849 1850 Respects the |server_base_url| configuration parameter, if present. Falls 1851 back to construct the URL from the server hostname and port otherwise. 1852 1853 Returns: 1854 The URL to use for constructing URLs that get returned to clients. 1855 """ 1856 base_url = self.server_base_url 1857 if base_url is None: 1858 base_url = 'http://%s:%s' % self.server_address[:2] 1859 1860 return base_url 1861 1862 1863class PolicyServerRunner(testserver_base.TestServerRunner): 1864 1865 def __init__(self): 1866 super(PolicyServerRunner, self).__init__() 1867 1868 def create_server(self, server_data): 1869 data_dir = self.options.data_dir or '' 1870 config_file = (self.options.config_file or 1871 os.path.join(data_dir, 'device_management')) 1872 server = PolicyTestServer((self.options.host, self.options.port), 1873 data_dir, config_file, 1874 self.options.client_state_file, 1875 self.options.policy_keys, 1876 self.options.rotate_keys_automatically, 1877 self.options.server_base_url) 1878 server_data['port'] = server.server_port 1879 return server 1880 1881 def add_options(self): 1882 testserver_base.TestServerRunner.add_options(self) 1883 self.option_parser.add_option('--client-state', dest='client_state_file', 1884 help='File that client state should be ' 1885 'persisted to. This allows the server to be ' 1886 'seeded by a list of pre-registered clients ' 1887 'and restarts without abandoning registered ' 1888 'clients.') 1889 self.option_parser.add_option('--policy-key', action='append', 1890 dest='policy_keys', 1891 help='Specify a path to a PEM-encoded ' 1892 'private key to use for policy signing. May ' 1893 'be specified multiple times in order to ' 1894 'load multiple keys into the server. The ' 1895 'server will use a canned key if none is ' 1896 'specified on the command line. The test ' 1897 'server will also look for a verification ' 1898 'signature file in the same location: ' 1899 '<filename>.sig and if present will add the ' 1900 'signature to the policy blob as appropriate ' 1901 'via the ' 1902 'new_public_key_verification_signature_deprecated ' 1903 'field.') 1904 self.option_parser.add_option('--rotate-policy-keys-automatically', 1905 action='store_true', 1906 dest='rotate_keys_automatically', 1907 help='If present, then the policy keys will ' 1908 'be rotated in a round-robin fashion for ' 1909 'each policy request (by default, either the ' 1910 'key specified in the config or the first ' 1911 'key will be used for all requests).') 1912 self.option_parser.add_option('--log-level', dest='log_level', 1913 default='WARN', 1914 help='Log level threshold to use.') 1915 self.option_parser.add_option('--config-file', dest='config_file', 1916 help='Specify a configuration file to use ' 1917 'instead of the default ' 1918 '<data_dir>/device_management') 1919 self.option_parser.add_option('--server-base-url', dest='server_base_url', 1920 help='The server base URL to use when ' 1921 'constructing URLs to return to the client.') 1922 1923 def run_server(self): 1924 logger = logging.getLogger() 1925 logger.setLevel(getattr(logging, str(self.options.log_level).upper())) 1926 if (self.options.log_to_console): 1927 logger.addHandler(logging.StreamHandler()) 1928 if (self.options.log_file): 1929 logger.addHandler(logging.FileHandler(self.options.log_file)) 1930 1931 testserver_base.TestServerRunner.run_server(self) 1932 1933 1934if __name__ == '__main__': 1935 sys.exit(PolicyServerRunner().main()) 1936