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