device_management.py revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
14a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch#!/usr/bin/python2.5
272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen# Copyright (c) 2011 The Chromium Authors. All rights reserved.
34a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch# Use of this source code is governed by a BSD-style license that can be
44a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch# found in the LICENSE file.
54a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
64a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch"""A bare-bones test server for testing cloud policy support.
74a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
84a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben MurdochThis implements a simple cloud policy test server that can be used to test
94a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochchrome's device management service client. The policy information is read from
1072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenthe file named device_management in the server's data directory. It contains
1172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenenforced and recommended policies for the device and user scope, and a list
1272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenof managed users.
1372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
1472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian MonsenThe format of the file is JSON. The root dictionary contains a list under the
1572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenkey "managed_users". It contains auth tokens for which the server will claim
1672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenthat the user is managed. The token string "*" indicates that all users are
1772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenclaimed to be managed. Other keys in the root dictionary identify request
1872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenscopes. Each request scope is described by a dictionary that holds two
1972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsensub-dictionaries: "mandatory" and "recommended". Both these hold the policy
2072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsendefinitions as key/value stores, their format is identical to what the Linux
2172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenimplementation reads from /etc.
2272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
2372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian MonsenExample:
244a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
254a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch{
2672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  "chromeos/device": {
2772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    "mandatory": {
2872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      "HomepageLocation" : "http://www.chromium.org"
2972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    },
3072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    "recommended": {
3172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      "JavascriptEnabled": false,
3272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    },
3372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  },
3472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  "managed_users": [
3572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    "secret123456"
3672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  ]
374a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch}
384a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
3972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
404a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch"""
414a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
424a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochimport cgi
434a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochimport logging
4472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenimport os
454a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochimport random
464a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochimport re
474a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochimport sys
4872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenimport time
4972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenimport tlslite
5072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenimport tlslite.api
514a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
524a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch# The name and availability of the json module varies in python versions.
534a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochtry:
544a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  import simplejson as json
554a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochexcept ImportError:
564a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  try:
574a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    import json
584a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  except ImportError:
594a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    json = None
604a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
614a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochimport device_management_backend_pb2 as dm
6272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenimport cloud_policy_pb2 as cp
6372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
644a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
654a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochclass RequestHandler(object):
664a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  """Decodes and handles device management requests from clients.
674a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
684a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  The handler implements all the request parsing and protobuf message decoding
694a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  and encoding. It calls back into the server to lookup, register, and
704a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  unregister clients.
714a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  """
724a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
734a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def __init__(self, server, path, headers, request):
744a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Initialize the handler.
754a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
764a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
774a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      server: The TestServer object to use for (un)registering clients.
784a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      path: A string containing the request path and query parameters.
794a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      headers: A rfc822.Message-like object containing HTTP headers.
804a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      request: The request data received from the client as a string.
814a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
824a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._server = server
834a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._path = path
844a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._headers = headers
854a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._request = request
864a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._params = None
874a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
884a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def GetUniqueParam(self, name):
894a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Extracts a unique query parameter from the request.
904a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
914a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
924a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      name: Names the parameter to fetch.
934a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
944a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      The parameter value or None if the parameter doesn't exist or is not
954a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      unique.
964a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
974a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if not self._params:
984a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      self._params = cgi.parse_qs(self._path[self._path.find('?')+1:])
994a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1004a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    param_list = self._params.get(name, [])
1014a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if len(param_list) == 1:
1024a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return param_list[0]
1034a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return None;
1044a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1054a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def HandleRequest(self):
1064a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Handles a request.
1074a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1084a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Parses the data supplied at construction time and returns a pair indicating
1094a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    http status code and response data to be sent back to the client.
1104a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1114a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
1124a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      A tuple of HTTP status code and response data to send to the client.
1134a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
1144a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    rmsg = dm.DeviceManagementRequest()
1154a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    rmsg.ParseFromString(self._request)
1164a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1174a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self.DumpMessage('Request', rmsg)
1184a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1194a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    request_type = self.GetUniqueParam('request')
1204a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if request_type == 'register':
1214a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return self.ProcessRegister(rmsg.register_request)
1224a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    elif request_type == 'unregister':
1234a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return self.ProcessUnregister(rmsg.unregister_request)
1244a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    elif request_type == 'policy':
1254a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return self.ProcessPolicy(rmsg.policy_request)
12672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    elif request_type == 'cloud_policy':
12772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      return self.ProcessCloudPolicyRequest(rmsg.cloud_policy_request)
12872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    elif request_type == 'managed_check':
12972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      return self.ProcessManagedCheck(rmsg.managed_check_request)
1304a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    else:
1314a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return (400, 'Invalid request parameter')
1324a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
13372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  def CheckGoogleLogin(self):
13472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    """Extracts the GoogleLogin auth token from the HTTP request, and
13572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    returns it. Returns None if the token is not present.
13672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    """
13772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    match = re.match('GoogleLogin auth=(\\w+)',
13872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                     self._headers.getheader('Authorization', ''))
13972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if not match:
14072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      return None
14172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return match.group(1)
14272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
14372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  def GetDeviceName(self):
14472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    """Returns the name for the currently authenticated device based on its
14572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    device id.
14672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    """
14772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return 'chromeos-' + self.GetUniqueParam('deviceid')
14872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
1494a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def ProcessRegister(self, msg):
1504a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Handles a register request.
1514a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1524a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Checks the query for authorization and device identifier, registers the
1534a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    device with the server and constructs a response.
1544a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1554a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
1564a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      msg: The DeviceRegisterRequest message received from the client.
1574a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1584a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
1594a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      A tuple of HTTP status code and response data to send to the client.
1604a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
1614a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Check the auth token and device ID.
16272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if not self.CheckGoogleLogin():
1634a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return (403, 'No authorization')
1644a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1654a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    device_id = self.GetUniqueParam('deviceid')
1664a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if not device_id:
1674a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return (400, 'Missing device identifier')
1684a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1694a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Register the device and create a token.
1704a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    dmtoken = self._server.RegisterDevice(device_id)
1714a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1724a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Send back the reply.
1734a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response = dm.DeviceManagementResponse()
1744a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response.error = dm.DeviceManagementResponse.SUCCESS
1754a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response.register_response.device_management_token = dmtoken
17672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    response.register_response.device_name = self.GetDeviceName()
1774a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1784a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self.DumpMessage('Response', response)
1794a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1804a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return (200, response.SerializeToString())
1814a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1824a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def ProcessUnregister(self, msg):
1834a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Handles a register request.
1844a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1854a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Checks for authorization, unregisters the device and constructs the
1864a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response.
1874a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1884a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
1894a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      msg: The DeviceUnregisterRequest message received from the client.
1904a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1914a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
1924a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      A tuple of HTTP status code and response data to send to the client.
1934a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
1944a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Check the management token.
1954a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    token, response = self.CheckToken();
1964a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if not token:
1974a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return response
1984a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1994a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Unregister the device.
2004a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._server.UnregisterDevice(token);
2014a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2024a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Prepare and send the response.
2034a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response = dm.DeviceManagementResponse()
2044a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response.error = dm.DeviceManagementResponse.SUCCESS
2054a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response.unregister_response.CopyFrom(dm.DeviceUnregisterResponse())
2064a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2074a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self.DumpMessage('Response', response)
2084a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2094a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return (200, response.SerializeToString())
2104a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
21172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  def ProcessManagedCheck(self, msg):
21272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    """Handles a 'managed check' request.
21372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
21472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Queries the list of managed users and responds the client if their user
21572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    is managed or not.
21672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
21772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Args:
21872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      msg: The ManagedCheckRequest message received from the client.
21972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
22072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Returns:
22172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      A tuple of HTTP status code and response data to send to the client.
22272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    """
22372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    # Check the management token.
22472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    auth = self.CheckGoogleLogin()
22572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if not auth:
22672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      return (403, 'No authorization')
22772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
22872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    managed_check_response = dm.ManagedCheckResponse()
22972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if ('*' in self._server.policy['managed_users'] or
23072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        auth in self._server.policy['managed_users']):
23172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      managed_check_response.mode = dm.ManagedCheckResponse.MANAGED;
23272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    else:
23372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      managed_check_response.mode = dm.ManagedCheckResponse.UNMANAGED;
23472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
23572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    # Prepare and send the response.
23672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    response = dm.DeviceManagementResponse()
23772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    response.error = dm.DeviceManagementResponse.SUCCESS
23872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    response.managed_check_response.CopyFrom(managed_check_response)
23972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
24072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    self.DumpMessage('Response', response)
24172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
24272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return (200, response.SerializeToString())
24372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
2444a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def ProcessPolicy(self, msg):
2454a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Handles a policy request.
2464a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2474a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Checks for authorization, encodes the policy into protobuf representation
24872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    and constructs the response.
2494a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2504a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
2514a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      msg: The DevicePolicyRequest message received from the client.
2524a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2534a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
2544a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      A tuple of HTTP status code and response data to send to the client.
2554a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
2564a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Check the management token.
2574a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    token, response = self.CheckToken()
2584a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if not token:
2594a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return response
2604a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2614a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Stuff the policy dictionary into a response message and send it back.
2624a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response = dm.DeviceManagementResponse()
2634a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response.error = dm.DeviceManagementResponse.SUCCESS
2644a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response.policy_response.CopyFrom(dm.DevicePolicyResponse())
2654a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2664a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Respond only if the client requested policy for the cros/device scope,
2674a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # since that's where chrome policy is supposed to live in.
26872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if msg.policy_scope in self._server.policy:
26972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      policy = self._server.policy[msg.policy_scope]['mandatory']
2704a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      setting = response.policy_response.setting.add()
2714a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      setting.policy_key = 'chrome-policy'
2724a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      policy_value = dm.GenericSetting()
27372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      for (key, value) in policy.iteritems():
2744a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        entry = policy_value.named_value.add()
2754a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        entry.name = key
2764a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        entry_value = dm.GenericValue()
2774a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        if isinstance(value, bool):
2784a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.value_type = dm.GenericValue.VALUE_TYPE_BOOL
2794a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.bool_value = value
2804a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        elif isinstance(value, int):
2814a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.value_type = dm.GenericValue.VALUE_TYPE_INT64
2824a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.int64_value = value
2834a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        elif isinstance(value, str) or isinstance(value, unicode):
2844a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.value_type = dm.GenericValue.VALUE_TYPE_STRING
2854a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.string_value = value
2864a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        elif isinstance(value, list):
2874a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.value_type = dm.GenericValue.VALUE_TYPE_STRING_ARRAY
2884a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          for list_entry in value:
2894a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch            entry_value.string_array.append(str(list_entry))
2904a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        entry.value.CopyFrom(entry_value)
2914a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      setting.policy_value.CopyFrom(policy_value)
2924a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2934a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self.DumpMessage('Response', response)
2944a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2954a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return (200, response.SerializeToString())
2964a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
29772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  def SetProtobufMessageField(self, group_message, field, field_value):
29872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    '''Sets a field in a protobuf message.
29972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
30072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Args:
30172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      group_message: The protobuf message.
30272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      field: The field of the message to set, it shuold be a member of
30372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          group_message.DESCRIPTOR.fields.
30472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      field_value: The value to set.
30572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    '''
30672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if field.label == field.LABEL_REPEATED:
30772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      assert type(field_value) == list
30872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      assert field.type == field.TYPE_STRING
30972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      list_field = group_message.__getattribute__(field.name)
31072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      for list_item in field_value:
31172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        list_field.append(list_item)
31272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    else:
31372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # Simple cases:
31472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      if field.type == field.TYPE_BOOL:
31572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        assert type(field_value) == bool
31672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      elif field.type == field.TYPE_STRING:
31772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        assert type(field_value) == str
31872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      elif field.type == field.TYPE_INT64:
31972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        assert type(field_value) == int
32072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      else:
32172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        raise Exception('Unknown field type %s' % field.type_name)
32272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      group_message.__setattr__(field.name, field_value)
32372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
32472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  def GatherPolicySettings(self, settings, policies):
32572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    '''Copies all the policies from a dictionary into a protobuf of type
32672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    CloudPolicySettings.
32772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
32872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Args:
32972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      settings: The destination: a CloudPolicySettings protobuf.
33072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      policies: The source: a dictionary containing policies under keys
33172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          'recommended' and 'mandatory'.
33272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    '''
33372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    for group in settings.DESCRIPTOR.fields:
33472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # Create protobuf message for group.
33572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      group_message = eval('cp.' + group.message_type.name + '()')
33672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # We assume that this policy group will be recommended, and only switch
33772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # it to mandatory if at least one of its members is mandatory.
33872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      group_message.policy_options.mode = cp.PolicyOptions.RECOMMENDED
33972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # Indicates if at least one field was set in |group_message|.
34072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      got_fields = False
34172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # Iterate over fields of the message and feed them from the
34272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # policy config file.
34372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      for field in group_message.DESCRIPTOR.fields:
34472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        field_value = None
34572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        if field.name in policies['mandatory']:
34672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          group_message.policy_options.mode = cp.PolicyOptions.MANDATORY
34772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          field_value = policies['mandatory'][field.name]
34872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        elif field.name in policies['recommended']:
34972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          field_value = policies['recommended'][field.name]
35072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        if field_value != None:
35172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          got_fields = True
35272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          self.SetProtobufMessageField(group_message, field, field_value)
35372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      if got_fields:
35472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        settings.__getattribute__(group.name).CopyFrom(group_message)
35572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
35672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  def ProcessCloudPolicyRequest(self, msg):
35772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    """Handles a cloud policy request. (New protocol for policy requests.)
35872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
35972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Checks for authorization, encodes the policy into protobuf representation,
36072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    signs it and constructs the repsonse.
36172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
36272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Args:
36372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      msg: The CloudPolicyRequest message received from the client.
36472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
36572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Returns:
36672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      A tuple of HTTP status code and response data to send to the client.
36772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    """
36872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    token, response = self.CheckToken()
36972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if not token:
37072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      return response
37172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
37272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    settings = cp.CloudPolicySettings()
37372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
37472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if msg.policy_scope in self._server.policy:
37572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # Respond is only given if the scope is specified in the config file.
37672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # Normally 'chromeos/device' and 'chromeos/user' should be accepted.
37772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      self.GatherPolicySettings(settings,
37872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                                self._server.policy[msg.policy_scope])
37972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
38072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    # Construct response
38172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    signed_response = dm.SignedCloudPolicyResponse()
38272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    signed_response.settings.CopyFrom(settings)
38372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    signed_response.timestamp = int(time.time())
38472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    signed_response.request_token = token;
38572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    signed_response.device_name = self.GetDeviceName()
38672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
38772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    cloud_response = dm.CloudPolicyResponse()
38872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    cloud_response.signed_response = signed_response.SerializeToString()
38972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    signed_data = cloud_response.signed_response
39072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    cloud_response.signature = (
39172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        self._server.private_key.hashAndSign(signed_data).tostring())
39272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    for certificate in self._server.cert_chain:
39372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      cloud_response.certificate_chain.append(
39472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          certificate.writeBytes().tostring())
39572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
39672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    response = dm.DeviceManagementResponse()
39772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    response.error = dm.DeviceManagementResponse.SUCCESS
39872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    response.cloud_policy_response.CopyFrom(cloud_response)
39972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
40072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    self.DumpMessage('Response', response)
40172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
40272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return (200, response.SerializeToString())
40372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
4044a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def CheckToken(self):
4054a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Helper for checking whether the client supplied a valid DM token.
4064a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
4074a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Extracts the token from the request and passed to the server in order to
4084a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    look up the client. Returns a pair of token and error response. If the token
4094a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    is None, the error response is a pair of status code and error message.
4104a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
4114a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
4124a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      A pair of DM token and error response. If the token is None, the message
4134a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      will contain the error response to send back.
4144a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
4154a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    error = None
416201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    dmtoken = None
417201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    request_device_id = self.GetUniqueParam('deviceid')
4184a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    match = re.match('GoogleDMToken token=(\\w+)',
4194a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch                     self._headers.getheader('Authorization', ''))
4204a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if match:
4214a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      dmtoken = match.group(1)
422201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    if not dmtoken:
423201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      error = dm.DeviceManagementResponse.DEVICE_MANAGEMENT_TOKEN_INVALID
424201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    elif (not request_device_id or
425201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch          not self._server.LookupDevice(dmtoken) == request_device_id):
426201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      error = dm.DeviceManagementResponse.DEVICE_NOT_FOUND
427201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    else:
428201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      return (dmtoken, None)
4294a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
4304a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response = dm.DeviceManagementResponse()
4314a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response.error = error
4324a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
4334a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self.DumpMessage('Response', response)
4344a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
4354a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return (None, (200, response.SerializeToString()))
4364a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
4374a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def DumpMessage(self, label, msg):
4384a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Helper for logging an ASCII dump of a protobuf message."""
4394a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    logging.debug('%s\n%s' % (label, str(msg)))
4404a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
4414a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochclass TestServer(object):
4424a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  """Handles requests and keeps global service state."""
4434a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
44472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  def __init__(self, policy_path, policy_cert_chain):
4454a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Initializes the server.
4464a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
4474a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
4484a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      policy_path: Names the file to read JSON-formatted policy from.
44972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      policy_cert_chain: List of paths to X.509 certificate files of the
45072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          certificate chain used for signing responses.
4514a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
4524a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._registered_devices = {}
4534a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self.policy = {}
4544a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if json is None:
4554a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      print 'No JSON module, cannot parse policy information'
4564a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    else :
4574a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      try:
4584a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        self.policy = json.loads(open(policy_path).read())
4594a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      except IOError:
4604a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        print 'Failed to load policy from %s' % policy_path
4614a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
46272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    self.private_key = None
46372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    self.cert_chain = []
46472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    for cert_path in policy_cert_chain:
46572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      try:
46672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        cert_text = open(cert_path).read()
46772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      except IOError:
46872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        print 'Failed to load certificate from %s' % cert_path
46972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      certificate = tlslite.api.X509()
47072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      certificate.parse(cert_text)
47172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      self.cert_chain.append(certificate)
47272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      if self.private_key is None:
47372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        self.private_key = tlslite.api.parsePEMKey(cert_text, private=True)
47472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        assert self.private_key != None
47572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
4764a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def HandleRequest(self, path, headers, request):
4774a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Handles a request.
4784a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
4794a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
4804a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      path: The request path and query parameters received from the client.
4814a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      headers: A rfc822.Message-like object containing HTTP headers.
4824a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      request: The request data received from the client as a string.
4834a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
4844a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      A pair of HTTP status code and response data to send to the client.
4854a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
4864a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    handler = RequestHandler(self, path, headers, request)
4874a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return handler.HandleRequest()
4884a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
4894a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def RegisterDevice(self, device_id):
4904a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Registers a device and generate a DM token for it.
4914a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
4924a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
4934a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      device_id: The device identifier provided by the client.
4944a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
4954a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
4964a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      The newly generated device token for the device.
4974a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
4984a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    dmtoken_chars = []
4994a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    while len(dmtoken_chars) < 32:
5004a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      dmtoken_chars.append(random.choice('0123456789abcdef'))
5014a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    dmtoken= ''.join(dmtoken_chars)
5024a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._registered_devices[dmtoken] = device_id
5034a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return dmtoken
5044a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
5054a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def LookupDevice(self, dmtoken):
5064a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Looks up a device by DMToken.
5074a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
5084a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
5094a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      dmtoken: The device management token provided by the client.
5104a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
5114a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
5124a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      The corresponding device identifier or None if not found.
5134a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
5144a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return self._registered_devices.get(dmtoken, None)
5154a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
5164a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def UnregisterDevice(self, dmtoken):
5174a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Unregisters a device identified by the given DM token.
5184a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
5194a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
5204a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      dmtoken: The device management token provided by the client.
5214a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
5224a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if dmtoken in self._registered_devices:
5234a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      del self._registered_devices[dmtoken]
524