15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""A bare-bones test server for testing cloud policy support.
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)This implements a simple cloud policy test server that can be used to test
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)chrome's device management service client. The policy information is read from
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)the file named device_management in the server's data directory. It contains
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)enforced and recommended policies for the device and user scope, and a list
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)of managed users.
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The format of the file is JSON. The root dictionary contains a list under the
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)key "managed_users". It contains auth tokens for which the server will claim
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)that the user is managed. The token string "*" indicates that all users are
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)claimed to be managed. Other keys in the root dictionary identify request
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)scopes. The user-request scope is described by a dictionary that holds two
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)sub-dictionaries: "mandatory" and "recommended". Both these hold the policy
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)definitions as key/value stores, their format is identical to what the Linux
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)implementation reads from /etc.
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The device-scope holds the policy-definition directly as key/value stores in the
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)protobuf-format.
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Example:
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles){
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  "google/chromeos/device" : {
282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    "guest_mode_enabled" : false
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  },
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  "google/chromeos/user" : {
312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    "mandatory" : {
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      "HomepageLocation" : "http://www.chromium.org",
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      "IncognitoEnabled" : false
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    },
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     "recommended" : {
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      "JavascriptEnabled": false
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  },
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  "google/chromeos/publicaccount/user@example.com" : {
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    "mandatory" : {
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      "HomepageLocation" : "http://www.chromium.org"
422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    },
432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     "recommended" : {
442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  },
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  "managed_users" : [
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    "secret123456"
482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  ],
49c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  "current_key_index": 0,
50c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  "robot_api_auth_code": "fake_auth_code"
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import BaseHTTPServer
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import cgi
572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import google.protobuf.text_format
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import hashlib
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import random
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import re
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import tlslite
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import tlslite.api
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import tlslite.utils
682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import tlslite.utils.cryptomath
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The name and availability of the json module varies in python versions.
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)try:
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  import simplejson as json
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)except ImportError:
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  try:
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    import json
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  except ImportError:
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    json = None
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import asn1der
802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import testserver_base
812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import device_management_backend_pb2 as dm
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import cloud_policy_pb2 as cp
842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import chrome_extension_policy_pb2 as ep
852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Device policy is only available on Chrome OS builds.
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)try:
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  import chrome_device_policy_pb2 as dp
892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)except ImportError:
902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  dp = None
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# ASN.1 object identifier for PKCS#1/RSA.
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)PKCS1_RSA_OID = '\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01'
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# SHA256 sum of "0".
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SHA256_0 = hashlib.sha256('0').digest()
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# List of bad machine identifiers that trigger the |valid_serial_number_missing|
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# flag to be set set in the policy fetch response.
1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)BAD_MACHINE_IDS = [ '123490EN400015' ]
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# List of machines that trigger the server to send kiosk enrollment response
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# for the register request.
1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)KIOSK_MACHINE_IDS = [ 'KIOSK' ]
1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)class PolicyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Decodes and handles device management requests from clients.
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  The handler implements all the request parsing and protobuf message decoding
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  and encoding. It calls back into the server to lookup, register, and
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  unregister clients.
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def __init__(self, request, client_address, server):
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Initialize the handler.
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      request: The request data received from the client as a string.
1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      client_address: The client address.
1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      server: The TestServer object to use for (un)registering clients.
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                                   client_address, server)
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetUniqueParam(self, name):
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Extracts a unique query parameter from the request.
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      name: Names the parameter to fetch.
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The parameter value or None if the parameter doesn't exist or is not
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      unique.
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if not hasattr(self, '_params'):
1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self._params = cgi.parse_qs(self.path[self.path.find('?') + 1:])
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    param_list = self._params.get(name, [])
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if len(param_list) == 1:
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return param_list[0]
1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return None
1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def do_GET(self):
1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Handles GET requests.
1452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Currently this is only used to serve external policy data."""
1472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    sep = self.path.find('?')
1482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    path = self.path if sep == -1 else self.path[:sep]
1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if path == '/externalpolicydata':
1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      http_response, raw_reply = self.HandleExternalPolicyDataRequest()
1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    else:
1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      http_response = 404
1532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      raw_reply = 'Invalid path'
1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.send_response(http_response)
1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.end_headers()
1562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.wfile.write(raw_reply)
1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def do_POST(self):
1592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    http_response, raw_reply = self.HandleRequest()
1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.send_response(http_response)
1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (http_response == 200):
1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.send_header('Content-Type', 'application/x-protobuffer')
1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.end_headers()
1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.wfile.write(raw_reply)
1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def HandleExternalPolicyDataRequest(self):
1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Handles a request to download policy data for a component."""
1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    policy_key = self.GetUniqueParam('key')
1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if not policy_key:
1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return (400, 'Missing key parameter')
1712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    data = self.server.ReadPolicyDataFromDataDir(policy_key)
1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if data is None:
1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return (404, 'Policy not found for ' + policy_key)
1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return (200, data)
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def HandleRequest(self):
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Handles a request.
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Parses the data supplied at construction time and returns a pair indicating
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    http status code and response data to be sent back to the client.
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      A tuple of HTTP status code and response data to send to the client.
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rmsg = dm.DeviceManagementRequest()
1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    length = int(self.headers.getheader('content-length'))
1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    rmsg.ParseFromString(self.rfile.read(length))
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.debug('gaia auth token -> ' +
1902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                  self.headers.getheader('Authorization', ''))
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.debug('oauth token -> ' + str(self.GetUniqueParam('oauth_token')))
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.debug('deviceid -> ' + str(self.GetUniqueParam('deviceid')))
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.DumpMessage('Request', rmsg)
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    request_type = self.GetUniqueParam('request')
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Check server side requirements, as defined in
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # device_management_backend.proto.
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (self.GetUniqueParam('devicetype') != '2' or
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.GetUniqueParam('apptype') != 'Chrome' or
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (request_type != 'ping' and
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         len(self.GetUniqueParam('deviceid')) >= 64) or
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        len(self.GetUniqueParam('agent')) >= 64):
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (400, 'Invalid request parameter')
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if request_type == 'register':
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return self.ProcessRegister(rmsg.register_request)
206c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if request_type == 'api_authorization':
207c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return self.ProcessApiAuthorization(rmsg.service_api_access_request)
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif request_type == 'unregister':
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return self.ProcessUnregister(rmsg.unregister_request)
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif request_type == 'policy' or request_type == 'ping':
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return self.ProcessPolicy(rmsg.policy_request, request_type)
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif request_type == 'enterprise_check':
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return self.ProcessAutoEnrollment(rmsg.auto_enrollment_request)
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (400, 'Invalid request parameter')
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def CreatePolicyForExternalPolicyData(self, policy_key):
2182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Returns an ExternalPolicyData protobuf for policy_key.
2192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    If there is policy data for policy_key then the download url will be
2212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    set so that it points to that data, and the appropriate hash is also set.
2222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Otherwise, the protobuf will be empty.
2232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Args:
2252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      policy_key: the policy type and settings entity id, joined by '/'.
2262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Returns:
2282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      A serialized ExternalPolicyData.
2292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """
2302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    settings = ep.ExternalPolicyData()
2312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    data = self.server.ReadPolicyDataFromDataDir(policy_key)
2322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if data:
2332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      settings.download_url = ('http://%s:%s/externalpolicydata?key=%s' %
2342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  (self.server.server_name,
2352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                   self.server.server_port,
2362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                   policy_key) )
2372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      settings.secure_hash = hashlib.sha1(data).digest()
2382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return settings.SerializeToString()
2392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def CheckGoogleLogin(self):
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Extracts the auth token from the request and returns it. The token may
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    either be a GoogleLogin token from an Authorization header, or an OAuth V2
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    token from the oauth_token query parameter. Returns None if no token is
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    present.
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    oauth_token = self.GetUniqueParam('oauth_token')
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if oauth_token:
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return oauth_token
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    match = re.match('GoogleLogin auth=(\\w+)',
2512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                     self.headers.getheader('Authorization', ''))
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if match:
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return match.group(1)
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return None
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ProcessRegister(self, msg):
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Handles a register request.
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Checks the query for authorization and device identifier, registers the
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    device with the server and constructs a response.
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      msg: The DeviceRegisterRequest message received from the client.
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      A tuple of HTTP status code and response data to send to the client.
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Check the auth token and device ID.
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    auth = self.CheckGoogleLogin()
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not auth:
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (403, 'No authorization')
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    policy = self.server.GetPolicies()
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ('*' not in policy['managed_users'] and
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        auth not in policy['managed_users']):
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (403, 'Unmanaged')
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    device_id = self.GetUniqueParam('deviceid')
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not device_id:
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (400, 'Missing device identifier')
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    token_info = self.server.RegisterDevice(device_id,
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                             msg.machine_id,
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                             msg.type)
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Send back the reply.
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    response = dm.DeviceManagementResponse()
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    response.register_response.device_management_token = (
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        token_info['device_token'])
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    response.register_response.machine_name = token_info['machine_name']
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    response.register_response.enrollment_type = token_info['enrollment_mode']
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.DumpMessage('Response', response)
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (200, response.SerializeToString())
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
298c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def ProcessApiAuthorization(self, msg):
299c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    """Handles an API authorization request.
300c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
301c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    Args:
302c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      msg: The DeviceServiceApiAccessRequest message received from the client.
303c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
304c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    Returns:
305c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      A tuple of HTTP status code and response data to send to the client.
306c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    """
307c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    policy = self.server.GetPolicies()
308c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
309c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # Return the auth code from the config file if it's defined,
310c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # else return a descriptive default value.
311c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    response = dm.DeviceManagementResponse()
312c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    response.service_api_access_response.auth_code = policy.get(
313bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch        'robot_api_auth_code', 'policy_testserver.py-auth_code')
314c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    self.DumpMessage('Response', response)
315c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
316c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    return (200, response.SerializeToString())
317c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ProcessUnregister(self, msg):
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Handles a register request.
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Checks for authorization, unregisters the device and constructs the
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    response.
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      msg: The DeviceUnregisterRequest message received from the client.
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      A tuple of HTTP status code and response data to send to the client.
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Check the management token.
3312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    token, response = self.CheckToken()
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not token:
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return response
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Unregister the device.
3362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.server.UnregisterDevice(token['device_token'])
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Prepare and send the response.
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    response = dm.DeviceManagementResponse()
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    response.unregister_response.CopyFrom(dm.DeviceUnregisterResponse())
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.DumpMessage('Response', response)
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (200, response.SerializeToString())
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ProcessPolicy(self, msg, request_type):
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Handles a policy request.
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Checks for authorization, encodes the policy into protobuf representation
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    and constructs the response.
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      msg: The DevicePolicyRequest message received from the client.
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      A tuple of HTTP status code and response data to send to the client.
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
3582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    token_info, error = self.CheckToken()
3592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if not token_info:
3602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return error
3612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    response = dm.DeviceManagementResponse()
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for request in msg.request:
3642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      fetch_response = response.policy_response.response.add()
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (request.policy_type in
3662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)             ('google/chrome/user',
3672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)              'google/chromeos/user',
3682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)              'google/chromeos/device',
3692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)              'google/chromeos/publicaccount',
3702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)              'google/chrome/extension')):
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if request_type != 'policy':
3722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          fetch_response.error_code = 400
3732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          fetch_response.error_message = 'Invalid request type'
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        else:
3752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          self.ProcessCloudPolicy(request, token_info, fetch_response)
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
3772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        fetch_response.error_code = 400
3782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        fetch_response.error_message = 'Invalid policy_type'
3792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return (200, response.SerializeToString())
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ProcessAutoEnrollment(self, msg):
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Handles an auto-enrollment check request.
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    The reply depends on the value of the modulus:
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      1: replies with no new modulus and the sha256 hash of "0"
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      2: replies with a new modulus, 4.
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      4: replies with a new modulus, 2.
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      8: fails with error 400.
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      16: replies with a new modulus, 16.
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      32: replies with a new modulus, 1.
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      anything else: replies with no new modulus and an empty list of hashes
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    These allow the client to pick the testing scenario its wants to simulate.
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      msg: The DeviceAutoEnrollmentRequest message received from the client.
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      A tuple of HTTP status code and response data to send to the client.
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    auto_enrollment_response = dm.DeviceAutoEnrollmentResponse()
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if msg.modulus == 1:
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      auto_enrollment_response.hash.append(SHA256_0)
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif msg.modulus == 2:
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      auto_enrollment_response.expected_modulus = 4
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif msg.modulus == 4:
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      auto_enrollment_response.expected_modulus = 2
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif msg.modulus == 8:
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return (400, 'Server error')
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif msg.modulus == 16:
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      auto_enrollment_response.expected_modulus = 16
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif msg.modulus == 32:
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      auto_enrollment_response.expected_modulus = 1
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    response = dm.DeviceManagementResponse()
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    response.auto_enrollment_response.CopyFrom(auto_enrollment_response)
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (200, response.SerializeToString())
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def SetProtobufMessageField(self, group_message, field, field_value):
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    '''Sets a field in a protobuf message.
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      group_message: The protobuf message.
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      field: The field of the message to set, it should be a member of
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          group_message.DESCRIPTOR.fields.
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      field_value: The value to set.
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    '''
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if field.label == field.LABEL_REPEATED:
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      assert type(field_value) == list
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entries = group_message.__getattribute__(field.name)
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if field.message_type is None:
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for list_item in field_value:
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          entries.append(list_item)
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # This field is itself a protobuf.
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        sub_type = field.message_type
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for sub_value in field_value:
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          assert type(sub_value) == dict
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # Add a new sub-protobuf per list entry.
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          sub_message = entries.add()
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # Now iterate over its fields and recursively add them.
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          for sub_field in sub_message.DESCRIPTOR.fields:
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if sub_field.name in sub_value:
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              value = sub_value[sub_field.name]
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              self.SetProtobufMessageField(sub_message, sub_field, value)
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif field.type == field.TYPE_BOOL:
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      assert type(field_value) == bool
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif field.type == field.TYPE_STRING:
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      assert type(field_value) == str or type(field_value) == unicode
4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif field.type == field.TYPE_INT64:
4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      assert type(field_value) == int
4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif (field.type == field.TYPE_MESSAGE and
4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          field.message_type.name == 'StringList'):
4575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      assert type(field_value) == list
4585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entries = group_message.__getattribute__(field.name).entries
4595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for list_item in field_value:
4605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        entries.append(list_item)
4615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return
4625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
4635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise Exception('Unknown field type %s' % field.type)
4645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    group_message.__setattr__(field.name, field_value)
4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GatherDevicePolicySettings(self, settings, policies):
4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    '''Copies all the policies from a dictionary into a protobuf of type
4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    CloudDeviceSettingsProto.
4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      settings: The destination ChromeDeviceSettingsProto protobuf.
4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      policies: The source dictionary containing policies in JSON format.
4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    '''
4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for group in settings.DESCRIPTOR.fields:
4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Create protobuf message for group.
4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      group_message = eval('dp.' + group.message_type.name + '()')
4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Indicates if at least one field was set in |group_message|.
4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      got_fields = False
4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Iterate over fields of the message and feed them from the
4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # policy config file.
4815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for field in group_message.DESCRIPTOR.fields:
4825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        field_value = None
4835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if field.name in policies:
4845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          got_fields = True
4855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          field_value = policies[field.name]
4865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          self.SetProtobufMessageField(group_message, field, field_value)
4875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if got_fields:
4885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        settings.__getattribute__(group.name).CopyFrom(group_message)
4895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GatherUserPolicySettings(self, settings, policies):
4915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    '''Copies all the policies from a dictionary into a protobuf of type
4925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    CloudPolicySettings.
4935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
4955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      settings: The destination: a CloudPolicySettings protobuf.
4965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      policies: The source: a dictionary containing policies under keys
4975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          'recommended' and 'mandatory'.
4985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    '''
4992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    for field in settings.DESCRIPTOR.fields:
5002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # |field| is the entry for a specific policy in the top-level
5012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # CloudPolicySettings proto.
5022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
5032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # Look for this policy's value in the mandatory or recommended dicts.
5042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if field.name in policies.get('mandatory', {}):
5052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        mode = cp.PolicyOptions.MANDATORY
5062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        value = policies['mandatory'][field.name]
5072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      elif field.name in policies.get('recommended', {}):
5082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        mode = cp.PolicyOptions.RECOMMENDED
5092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        value = policies['recommended'][field.name]
5102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      else:
5112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        continue
5122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
5132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # Create protobuf message for this policy.
5142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      policy_message = eval('cp.' + field.message_type.name + '()')
5152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      policy_message.policy_options.mode = mode
5162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      field_descriptor = policy_message.DESCRIPTOR.fields_by_name['value']
5172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.SetProtobufMessageField(policy_message, field_descriptor, value)
5182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      settings.__getattribute__(field.name).CopyFrom(policy_message)
5195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def ProcessCloudPolicy(self, msg, token_info, response):
5215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Handles a cloud policy request. (New protocol for policy requests.)
5225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Encodes the policy into protobuf representation, signs it and constructs
5242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    the response.
5255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
5275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      msg: The CloudPolicyRequest message received from the client.
5282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      token_info: the token extracted from the request.
5292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      response: A PolicyFetchResponse message that should be filled with the
5302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                response data.
5315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
5325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if msg.machine_id:
5342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.server.UpdateMachineId(token_info['device_token'], msg.machine_id)
5355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Response is only given if the scope is specified in the config file.
5372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Normally 'google/chromeos/device', 'google/chromeos/user' and
5382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # 'google/chromeos/publicaccount' should be accepted.
5392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    policy = self.server.GetPolicies()
5405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    policy_value = ''
5412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    policy_key = msg.policy_type
5422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if msg.settings_entity_id:
5432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      policy_key += '/' + msg.settings_entity_id
5442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if msg.policy_type in token_info['allowed_policy_types']:
5452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if (msg.policy_type == 'google/chromeos/user' or
5462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          msg.policy_type == 'google/chrome/user' or
5472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          msg.policy_type == 'google/chromeos/publicaccount'):
5485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        settings = cp.CloudPolicySettings()
5492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        payload = self.server.ReadPolicyFromDataDir(policy_key, settings)
5502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if payload is None:
5512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          self.GatherUserPolicySettings(settings, policy.get(policy_key, {}))
5522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          payload = settings.SerializeToString()
5532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      elif dp is not None and msg.policy_type == 'google/chromeos/device':
5545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        settings = dp.ChromeDeviceSettingsProto()
5552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        payload = self.server.ReadPolicyFromDataDir(policy_key, settings)
5562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if payload is None:
5572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          self.GatherDevicePolicySettings(settings, policy.get(policy_key, {}))
5582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          payload = settings.SerializeToString()
5592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      elif msg.policy_type == 'google/chrome/extension':
5602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        settings = ep.ExternalPolicyData()
5612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        payload = self.server.ReadPolicyFromDataDir(policy_key, settings)
5622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if payload is None:
5632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          payload = self.CreatePolicyForExternalPolicyData(policy_key)
5642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      else:
5652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        response.error_code = 400
5662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        response.error_message = 'Invalid policy type'
5672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        return
5682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    else:
5692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      response.error_code = 400
5702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      response.error_message = 'Request not allowed for the token used'
5712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return
5725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Sign with 'current_key_index', defaulting to key 0.
5745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    signing_key = None
5755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    req_key = None
5762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    current_key_index = policy.get('current_key_index', 0)
5772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    nkeys = len(self.server.keys)
5782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (msg.signature_type == dm.PolicyFetchRequest.SHA1_RSA and
5792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        current_key_index in range(nkeys)):
5802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      signing_key = self.server.keys[current_key_index]
5815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if msg.public_key_version in range(1, nkeys + 1):
5825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # requested key exists, use for signing and rotate.
5832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        req_key = self.server.keys[msg.public_key_version - 1]['private_key']
5845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Fill the policy data protobuf.
5865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    policy_data = dm.PolicyData()
5875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    policy_data.policy_type = msg.policy_type
5885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    policy_data.timestamp = int(time.time() * 1000)
5895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    policy_data.request_token = token_info['device_token']
5902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    policy_data.policy_value = payload
5915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    policy_data.machine_name = token_info['machine_name']
5925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    policy_data.valid_serial_number_missing = (
5935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        token_info['machine_id'] in BAD_MACHINE_IDS)
5942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    policy_data.settings_entity_id = msg.settings_entity_id
595bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch    policy_data.service_account_identity = policy.get(
596bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch        'service_account_identity',
597bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch        'policy_testserver.py-service_account_identity')
5985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if signing_key:
6002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      policy_data.public_key_version = current_key_index + 1
6012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if msg.policy_type == 'google/chromeos/publicaccount':
6022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      policy_data.username = msg.settings_entity_id
6032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    else:
6042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # For regular user/device policy, there is no way for the testserver to
6052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # know the user name belonging to the GAIA auth token we received (short
6062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # of actually talking to GAIA). To address this, we read the username from
6072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # the policy configuration dictionary, or use a default.
6082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      policy_data.username = policy.get('policy_user', 'user@example.com')
6095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    policy_data.device_id = token_info['device_id']
6105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    signed_data = policy_data.SerializeToString()
6115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    response.policy_data = signed_data
6135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if signing_key:
6142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      response.policy_data_signature = (
6155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          signing_key['private_key'].hashAndSign(signed_data).tostring())
6162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if msg.public_key_version != current_key_index + 1:
6172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        response.new_public_key = signing_key['public_key']
6185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if req_key:
6192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          response.new_public_key_signature = (
6202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)              req_key.hashAndSign(response.new_public_key).tostring())
6215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.DumpMessage('Response', response)
6235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (200, response.SerializeToString())
6255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def CheckToken(self):
6275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Helper for checking whether the client supplied a valid DM token.
6285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Extracts the token from the request and passed to the server in order to
6305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    look up the client.
6315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
6335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      A pair of token information record and error response. If the first
6345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      element is None, then the second contains an error code to send back to
6355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      the client. Otherwise the first element is the same structure that is
6365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      returned by LookupToken().
6375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
6385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    error = 500
6395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dmtoken = None
6405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    request_device_id = self.GetUniqueParam('deviceid')
6415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    match = re.match('GoogleDMToken token=(\\w+)',
6422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                     self.headers.getheader('Authorization', ''))
6435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if match:
6445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dmtoken = match.group(1)
6455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not dmtoken:
6465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      error = 401
6475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
6482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      token_info = self.server.LookupToken(dmtoken)
6495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (not token_info or
6505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          not request_device_id or
6515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          token_info['device_id'] != request_device_id):
6525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        error = 410
6535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
6545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return (token_info, None)
6555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.debug('Token check failed with error %d' % error)
6575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (None, (error, 'Server error %d' % error))
6595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def DumpMessage(self, label, msg):
6615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Helper for logging an ASCII dump of a protobuf message."""
6625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.debug('%s\n%s' % (label, str(msg)))
6635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
6652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)class PolicyTestServer(testserver_base.ClientRestrictingServerMixIn,
6662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                       testserver_base.BrokenPipeHandlerMixIn,
6672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                       testserver_base.StoppableHTTPServer):
6685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Handles requests and keeps global service state."""
6695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def __init__(self, server_address, data_dir, policy_path, client_state_file,
6712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)               private_key_paths):
6725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Initializes the server.
6735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
6752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      server_address: Server host and port.
6765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      policy_path: Names the file to read JSON-formatted policy from.
6775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      private_key_paths: List of paths to read private keys from.
6785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
6792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    testserver_base.StoppableHTTPServer.__init__(self, server_address,
6802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                                 PolicyRequestHandler)
6815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._registered_tokens = {}
6822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.data_dir = data_dir
6835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.policy_path = policy_path
6842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.client_state_file = client_state_file
6855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.keys = []
6875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if private_key_paths:
6885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Load specified keys from the filesystem.
6895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for key_path in private_key_paths:
6905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
6912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          key_str = open(key_path).read()
6925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except IOError:
6935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          print 'Failed to load private key from %s' % key_path
6945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          continue
6955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        try:
6972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          key = tlslite.api.parsePEMKey(key_str, private=True)
6982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        except SyntaxError:
6992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          key = tlslite.utils.Python_RSAKey.Python_RSAKey._parsePKCS8(
7002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)              tlslite.utils.cryptomath.stringToBytes(key_str))
7012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
7025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        assert key is not None
7035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.keys.append({ 'private_key' : key })
7045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
7052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # Generate 2 private keys if none were passed from the command line.
7062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      for i in range(2):
7072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        key = tlslite.api.generateRSAKey(512)
7082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        assert key is not None
7092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self.keys.append({ 'private_key' : key })
7105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Derive the public keys from the private keys.
7125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for entry in self.keys:
7135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      key = entry['private_key']
7145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      algorithm = asn1der.Sequence(
7165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          [ asn1der.Data(asn1der.OBJECT_IDENTIFIER, PKCS1_RSA_OID),
7175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            asn1der.Data(asn1der.NULL, '') ])
7185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      rsa_pubkey = asn1der.Sequence([ asn1der.Integer(key.n),
7195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                      asn1der.Integer(key.e) ])
7205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pubkey = asn1der.Sequence([ algorithm, asn1der.Bitstring(rsa_pubkey) ])
7212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      entry['public_key'] = pubkey
7222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
7232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Load client state.
7242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if self.client_state_file is not None:
7252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      try:
7262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        file_contents = open(self.client_state_file).read()
7272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self._registered_tokens = json.loads(file_contents)
7282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      except IOError:
7292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        pass
7305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetPolicies(self):
7325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Returns the policies to be used, reloaded form the backend file every
7335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       time this is called.
7345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
7355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    policy = {}
7365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if json is None:
7375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print 'No JSON module, cannot parse policy information'
7385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else :
7395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      try:
7405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        policy = json.loads(open(self.policy_path).read())
7415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      except IOError:
7425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        print 'Failed to load policy from %s' % self.policy_path
7435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return policy
7445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def RegisterDevice(self, device_id, machine_id, type):
7465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Registers a device or user and generates a DM token for it.
7475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
7495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      device_id: The device identifier provided by the client.
7505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
7525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The newly generated device token for the device.
7535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
7545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dmtoken_chars = []
7555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    while len(dmtoken_chars) < 32:
7565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dmtoken_chars.append(random.choice('0123456789abcdef'))
7575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dmtoken = ''.join(dmtoken_chars)
7585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    allowed_policy_types = {
7592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      dm.DeviceRegisterRequest.BROWSER: ['google/chrome/user'],
7602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      dm.DeviceRegisterRequest.USER: [
7612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          'google/chromeos/user',
7622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          'google/chrome/extension'
7632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      ],
7642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      dm.DeviceRegisterRequest.DEVICE: [
7652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          'google/chromeos/device',
7662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          'google/chromeos/publicaccount'
7672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      ],
7682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      dm.DeviceRegisterRequest.TT: ['google/chromeos/user',
7692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                    'google/chrome/user'],
7705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
7715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if machine_id in KIOSK_MACHINE_IDS:
7725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      enrollment_mode = dm.DeviceRegisterResponse.RETAIL
7735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
7745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      enrollment_mode = dm.DeviceRegisterResponse.ENTERPRISE
7755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._registered_tokens[dmtoken] = {
7765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'device_id': device_id,
7775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'device_token': dmtoken,
7785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'allowed_policy_types': allowed_policy_types[type],
7795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'machine_name': 'chromeos-' + machine_id,
7805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'machine_id': machine_id,
7815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'enrollment_mode': enrollment_mode,
7825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
7832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.WriteClientState()
7845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self._registered_tokens[dmtoken]
7855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def UpdateMachineId(self, dmtoken, machine_id):
7875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Updates the machine identifier for a registered device.
7885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
7905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dmtoken: The device management token provided by the client.
7915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      machine_id: Updated hardware identifier value.
7925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
7935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if dmtoken in self._registered_tokens:
7945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._registered_tokens[dmtoken]['machine_id'] = machine_id
7952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.WriteClientState()
7965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def LookupToken(self, dmtoken):
7985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Looks up a device or a user by DM token.
7995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
8015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dmtoken: The device management token provided by the client.
8025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
8045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      A dictionary with information about a device or user that is registered by
8055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dmtoken, or None if the token is not found.
8065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
8075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self._registered_tokens.get(dmtoken, None)
8085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def UnregisterDevice(self, dmtoken):
8105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Unregisters a device identified by the given DM token.
8115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
8135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dmtoken: The device management token provided by the client.
8145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
8155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if dmtoken in self._registered_tokens.keys():
8165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      del self._registered_tokens[dmtoken]
8172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.WriteClientState()
8182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def WriteClientState(self):
8202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Writes the client state back to the file."""
8212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if self.client_state_file is not None:
8222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      json_data = json.dumps(self._registered_tokens)
8232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      open(self.client_state_file, 'w').write(json_data)
8242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def GetBaseFilename(self, policy_selector):
8262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Returns the base filename for the given policy_selector.
8272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Args:
8292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      policy_selector: the policy type and settings entity id, joined by '/'.
8302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Returns:
8322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      The filename corresponding to the policy_selector, without a file
8332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      extension.
8342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """
8352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    sanitized_policy_selector = re.sub('[^A-Za-z0-9.@-]', '_', policy_selector)
8362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return os.path.join(self.data_dir or '',
8372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                        'policy_%s' % sanitized_policy_selector)
8382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def ReadPolicyFromDataDir(self, policy_selector, proto_message):
8402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Tries to read policy payload from a file in the data directory.
8412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    First checks for a binary rendition of the policy protobuf in
8432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    <data_dir>/policy_<sanitized_policy_selector>.bin. If that exists, returns
8442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    it. If that file doesn't exist, tries
8452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    <data_dir>/policy_<sanitized_policy_selector>.txt and decodes that as a
8462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    protobuf using proto_message. If that fails as well, returns None.
8472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Args:
8492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      policy_selector: Selects which policy to read.
8502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      proto_message: Optional protobuf message object used for decoding the
8512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          proto text format.
8522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Returns:
8542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      The binary payload message, or None if not found.
8552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """
8562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    base_filename = self.GetBaseFilename(policy_selector)
8572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Try the binary payload file first.
8592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    try:
8602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return open(base_filename + '.bin').read()
8612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    except IOError:
8622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      pass
8632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # If that fails, try the text version instead.
8652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if proto_message is None:
8662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return None
8672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    try:
8692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      text = open(base_filename + '.txt').read()
8702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      google.protobuf.text_format.Merge(text, proto_message)
8712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return proto_message.SerializeToString()
8722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    except IOError:
8732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return None
8742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    except google.protobuf.text_format.ParseError:
8752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return None
8762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def ReadPolicyDataFromDataDir(self, policy_selector):
8782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Returns the external policy data for |policy_selector| if found.
8792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Args:
8812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      policy_selector: Selects which policy to read.
8822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Returns:
8842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      The data for the corresponding policy type and entity id, if found.
8852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """
8862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    base_filename = self.GetBaseFilename(policy_selector)
8872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    try:
8882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return open(base_filename + '.data').read()
8892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    except IOError:
8902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return None
8912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)class PolicyServerRunner(testserver_base.TestServerRunner):
8942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def __init__(self):
8962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    super(PolicyServerRunner, self).__init__()
8972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def create_server(self, server_data):
8992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    data_dir = self.options.data_dir or ''
9002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    config_file = (self.options.config_file or
9012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                   os.path.join(data_dir, 'device_management'))
9022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    server = PolicyTestServer((self.options.host, self.options.port),
9032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                              data_dir, config_file,
9042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                              self.options.client_state_file,
9052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                              self.options.policy_keys)
9062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    server_data['port'] = server.server_port
9072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return server
9082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
9092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def add_options(self):
9102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    testserver_base.TestServerRunner.add_options(self)
9112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.option_parser.add_option('--client-state', dest='client_state_file',
9122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  help='File that client state should be '
9132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  'persisted to. This allows the server to be '
9142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  'seeded by a list of pre-registered clients '
9152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  'and restarts without abandoning registered '
9162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  'clients.')
9172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.option_parser.add_option('--policy-key', action='append',
9182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  dest='policy_keys',
9192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  help='Specify a path to a PEM-encoded '
9202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  'private key to use for policy signing. May '
9212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  'be specified multiple times in order to '
9222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  'load multipe keys into the server. If the '
9232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  'server has multiple keys, it will rotate '
9242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  'through them in at each request in a '
9252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  'round-robin fashion. The server will '
9262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  'generate a random key if none is specified '
9272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  'on the command line.')
9282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.option_parser.add_option('--log-level', dest='log_level',
9292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  default='WARN',
9302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  help='Log level threshold to use.')
9312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.option_parser.add_option('--config-file', dest='config_file',
9322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  help='Specify a configuration file to use '
9332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  'instead of the default '
9342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                  '<data_dir>/device_management')
9352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
9362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def run_server(self):
9372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    logger = logging.getLogger()
9382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    logger.setLevel(getattr(logging, str(self.options.log_level).upper()))
9392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (self.options.log_to_console):
9402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      logger.addHandler(logging.StreamHandler())
9412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (self.options.log_file):
9422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      logger.addHandler(logging.FileHandler(self.options.log_file))
9432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
9442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    testserver_base.TestServerRunner.run_server(self)
9452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
9462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
9472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)if __name__ == '__main__':
9482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  sys.exit(PolicyServerRunner().main())
949