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
51ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenimport tlslite.utils
524a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
534a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch# The name and availability of the json module varies in python versions.
544a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochtry:
554a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  import simplejson as json
564a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochexcept ImportError:
574a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  try:
584a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    import json
594a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  except ImportError:
604a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    json = None
614a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
62ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenimport asn1der
634a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochimport device_management_backend_pb2 as dm
6472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsenimport cloud_policy_pb2 as cp
65ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsenimport chrome_device_policy_pb2 as dp
6672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
67ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen# ASN.1 object identifier for PKCS#1/RSA.
68ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian MonsenPKCS1_RSA_OID = '\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01'
694a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
704a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochclass RequestHandler(object):
714a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  """Decodes and handles device management requests from clients.
724a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
734a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  The handler implements all the request parsing and protobuf message decoding
744a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  and encoding. It calls back into the server to lookup, register, and
754a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  unregister clients.
764a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  """
774a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
784a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def __init__(self, server, path, headers, request):
794a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Initialize the handler.
804a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
814a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
824a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      server: The TestServer object to use for (un)registering clients.
834a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      path: A string containing the request path and query parameters.
844a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      headers: A rfc822.Message-like object containing HTTP headers.
854a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      request: The request data received from the client as a string.
864a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
874a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._server = server
884a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._path = path
894a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._headers = headers
904a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._request = request
914a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._params = None
924a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
934a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def GetUniqueParam(self, name):
944a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Extracts a unique query parameter from the request.
954a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
964a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
974a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      name: Names the parameter to fetch.
984a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
994a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      The parameter value or None if the parameter doesn't exist or is not
1004a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      unique.
1014a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
1024a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if not self._params:
103dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      self._params = cgi.parse_qs(self._path[self._path.find('?') + 1:])
1044a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1054a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    param_list = self._params.get(name, [])
1064a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if len(param_list) == 1:
1074a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return param_list[0]
1084a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return None;
1094a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1104a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def HandleRequest(self):
1114a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Handles a request.
1124a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1134a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Parses the data supplied at construction time and returns a pair indicating
1144a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    http status code and response data to be sent back to the client.
1154a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1164a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
1174a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      A tuple of HTTP status code and response data to send to the client.
1184a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
1194a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    rmsg = dm.DeviceManagementRequest()
1204a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    rmsg.ParseFromString(self._request)
1214a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
122dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    logging.debug('auth -> ' + self._headers.getheader('Authorization', ''))
123dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    logging.debug('deviceid -> ' + self.GetUniqueParam('deviceid'))
1244a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self.DumpMessage('Request', rmsg)
1254a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1264a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    request_type = self.GetUniqueParam('request')
127dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    # Check server side requirements, as defined in
128dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    # device_management_backend.proto.
129dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    if (self.GetUniqueParam('devicetype') != '2' or
130dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        self.GetUniqueParam('apptype') != 'Chrome' or
131dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        (request_type != 'ping' and
132dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen         len(self.GetUniqueParam('deviceid')) >= 64) or
133dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        len(self.GetUniqueParam('agent')) >= 64):
134dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      return (400, 'Invalid request parameter')
1354a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if request_type == 'register':
1364a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return self.ProcessRegister(rmsg.register_request)
1374a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    elif request_type == 'unregister':
1384a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return self.ProcessUnregister(rmsg.unregister_request)
139dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    elif request_type == 'policy' or request_type == 'ping':
140dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      return self.ProcessPolicy(rmsg.policy_request, request_type)
1414a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    else:
1424a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return (400, 'Invalid request parameter')
1434a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
14472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  def CheckGoogleLogin(self):
14572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    """Extracts the GoogleLogin auth token from the HTTP request, and
14672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    returns it. Returns None if the token is not present.
14772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    """
14872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    match = re.match('GoogleLogin auth=(\\w+)',
14972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen                     self._headers.getheader('Authorization', ''))
15072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if not match:
15172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      return None
15272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return match.group(1)
15372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
1544a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def ProcessRegister(self, msg):
1554a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Handles a register request.
1564a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1574a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Checks the query for authorization and device identifier, registers the
1584a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    device with the server and constructs a response.
1594a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1604a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
1614a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      msg: The DeviceRegisterRequest message received from the client.
1624a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1634a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
1644a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      A tuple of HTTP status code and response data to send to the client.
1654a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
1664a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Check the auth token and device ID.
16772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if not self.CheckGoogleLogin():
1684a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return (403, 'No authorization')
1694a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1704a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    device_id = self.GetUniqueParam('deviceid')
1714a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if not device_id:
1724a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return (400, 'Missing device identifier')
1734a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
174dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    token_info = self._server.RegisterDevice(device_id,
175dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                                             msg.machine_id,
176dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen                                             msg.type)
1774a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1784a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Send back the reply.
1794a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response = dm.DeviceManagementResponse()
180dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    response.register_response.device_management_token = (
181dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        token_info['device_token'])
182dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    response.register_response.machine_name = token_info['machine_name']
1834a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1844a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self.DumpMessage('Response', response)
1854a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1864a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return (200, response.SerializeToString())
1874a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1884a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def ProcessUnregister(self, msg):
1894a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Handles a register request.
1904a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1914a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Checks for authorization, unregisters the device and constructs the
1924a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response.
1934a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1944a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
1954a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      msg: The DeviceUnregisterRequest message received from the client.
1964a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
1974a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
1984a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      A tuple of HTTP status code and response data to send to the client.
1994a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
2004a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Check the management token.
2014a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    token, response = self.CheckToken();
2024a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if not token:
2034a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return response
2044a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2054a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Unregister the device.
2064a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self._server.UnregisterDevice(token);
2074a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2084a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Prepare and send the response.
2094a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response = dm.DeviceManagementResponse()
2104a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response.unregister_response.CopyFrom(dm.DeviceUnregisterResponse())
2114a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2124a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self.DumpMessage('Response', response)
2134a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2144a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return (200, response.SerializeToString())
2154a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
216dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  def ProcessInitialPolicy(self, msg):
217dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    """Handles a 'preregister policy' request.
21872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
21972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Queries the list of managed users and responds the client if their user
22072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    is managed or not.
22172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
22272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Args:
223dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      msg: The PolicyFetchRequest message received from the client.
22472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
22572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Returns:
22672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      A tuple of HTTP status code and response data to send to the client.
22772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    """
228dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    # Check the GAIA token.
22972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    auth = self.CheckGoogleLogin()
23072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if not auth:
23172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      return (403, 'No authorization')
23272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
233dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    chrome_initial_settings = dm.ChromeInitialSettingsProto()
23472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    if ('*' in self._server.policy['managed_users'] or
23572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        auth in self._server.policy['managed_users']):
236dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      chrome_initial_settings.enrollment_provision = (
237dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen          dm.ChromeInitialSettingsProto.MANAGED);
23872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    else:
239dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      chrome_initial_settings.enrollment_provision = (
240dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen          dm.ChromeInitialSettingsProto.UNMANAGED);
241dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
242dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    policy_data = dm.PolicyData()
243dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    policy_data.policy_type = msg.policy_type
244dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    policy_data.policy_value = chrome_initial_settings.SerializeToString()
24572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
24672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    # Prepare and send the response.
24772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    response = dm.DeviceManagementResponse()
248dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    fetch_response = response.policy_response.response.add()
249dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    fetch_response.policy_data = (
250dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        policy_data.SerializeToString())
25172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
25272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    self.DumpMessage('Response', response)
25372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
25472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return (200, response.SerializeToString())
25572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
256dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  def ProcessDevicePolicy(self, msg):
257dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    """Handles a policy request that uses the deprecated protcol.
258dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    TODO(gfeher): Remove this when we certainly don't need it.
2594a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2604a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Checks for authorization, encodes the policy into protobuf representation
26172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    and constructs the response.
2624a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2634a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
2644a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      msg: The DevicePolicyRequest message received from the client.
2654a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2664a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
2674a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      A tuple of HTTP status code and response data to send to the client.
2684a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
269dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
2704a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Check the management token.
2714a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    token, response = self.CheckToken()
2724a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if not token:
2734a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      return response
2744a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2754a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Stuff the policy dictionary into a response message and send it back.
2764a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response = dm.DeviceManagementResponse()
2774a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response.policy_response.CopyFrom(dm.DevicePolicyResponse())
2784a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
2794a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # Respond only if the client requested policy for the cros/device scope,
2804a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    # since that's where chrome policy is supposed to live in.
281dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    if msg.policy_scope == 'chromeos/device':
282dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      policy = self._server.policy['google/chromeos/user']['mandatory']
2834a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      setting = response.policy_response.setting.add()
2844a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      setting.policy_key = 'chrome-policy'
2854a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      policy_value = dm.GenericSetting()
28672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      for (key, value) in policy.iteritems():
2874a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        entry = policy_value.named_value.add()
2884a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        entry.name = key
2894a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        entry_value = dm.GenericValue()
2904a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        if isinstance(value, bool):
2914a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.value_type = dm.GenericValue.VALUE_TYPE_BOOL
2924a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.bool_value = value
2934a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        elif isinstance(value, int):
2944a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.value_type = dm.GenericValue.VALUE_TYPE_INT64
2954a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.int64_value = value
2964a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        elif isinstance(value, str) or isinstance(value, unicode):
2974a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.value_type = dm.GenericValue.VALUE_TYPE_STRING
2984a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.string_value = value
2994a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        elif isinstance(value, list):
3004a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          entry_value.value_type = dm.GenericValue.VALUE_TYPE_STRING_ARRAY
3014a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch          for list_entry in value:
3024a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch            entry_value.string_array.append(str(list_entry))
3034a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        entry.value.CopyFrom(entry_value)
3044a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      setting.policy_value.CopyFrom(policy_value)
3054a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
3064a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self.DumpMessage('Response', response)
3074a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
3084a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return (200, response.SerializeToString())
3094a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
310dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  def ProcessPolicy(self, msg, request_type):
311dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    """Handles a policy request.
312dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
313dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    Checks for authorization, encodes the policy into protobuf representation
314dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    and constructs the response.
315dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
316dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    Args:
317dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      msg: The DevicePolicyRequest message received from the client.
318dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
319dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    Returns:
320dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      A tuple of HTTP status code and response data to send to the client.
321dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    """
322dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
323dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    if msg.request:
324dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      for request in msg.request:
325dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        if request.policy_type == 'google/chromeos/unregistered_user':
326dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen          if request_type != 'ping':
327dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen            return (400, 'Invalid request type')
328dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen          return self.ProcessInitialPolicy(request)
329dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        elif (request.policy_type in
330dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen              ('google/chromeos/user', 'google/chromeos/device')):
331dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen          if request_type != 'policy':
332dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen            return (400, 'Invalid request type')
333dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen          return self.ProcessCloudPolicy(request)
334dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        else:
335dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen          return (400, 'Invalid policy_type')
336dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    else:
337dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      return self.ProcessDevicePolicy(msg)
338dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
33972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen  def SetProtobufMessageField(self, group_message, field, field_value):
34072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    '''Sets a field in a protobuf message.
34172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
34272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Args:
34372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      group_message: The protobuf message.
34472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      field: The field of the message to set, it shuold be a member of
34572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          group_message.DESCRIPTOR.fields.
34672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      field_value: The value to set.
34772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    '''
348ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if field.label == field.LABEL_REPEATED:
349ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      assert type(field_value) == list
350ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      entries = group_message.__getattribute__(field.name)
351ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      for list_item in field_value:
352ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        entries.append(list_item)
353ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      return
354ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    elif field.type == field.TYPE_BOOL:
355dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      assert type(field_value) == bool
356dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    elif field.type == field.TYPE_STRING:
357ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      assert type(field_value) == str or type(field_value) == unicode
358dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    elif field.type == field.TYPE_INT64:
359dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      assert type(field_value) == int
360dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    elif (field.type == field.TYPE_MESSAGE and
361dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen          field.message_type.name == 'StringList'):
36272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      assert type(field_value) == list
363dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      entries = group_message.__getattribute__(field.name).entries
36472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      for list_item in field_value:
365dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        entries.append(list_item)
366dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      return
36772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    else:
368dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      raise Exception('Unknown field type %s' % field.type)
369dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    group_message.__setattr__(field.name, field_value)
37072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
371ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  def GatherDevicePolicySettings(self, settings, policies):
372ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    '''Copies all the policies from a dictionary into a protobuf of type
373ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    CloudDeviceSettingsProto.
374ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
375ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    Args:
376ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      settings: The destination ChromeDeviceSettingsProto protobuf.
377ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      policies: The source dictionary containing policies in JSON format.
378ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    '''
379ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    for group in settings.DESCRIPTOR.fields:
380ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      # Create protobuf message for group.
381ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      group_message = eval('dp.' + group.message_type.name + '()')
382ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      # Indicates if at least one field was set in |group_message|.
383ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      got_fields = False
384ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      # Iterate over fields of the message and feed them from the
385ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      # policy config file.
386ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      for field in group_message.DESCRIPTOR.fields:
387ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        field_value = None
388ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        if field.name in policies:
389ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          got_fields = True
390ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          field_value = policies[field.name]
391ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          self.SetProtobufMessageField(group_message, field, field_value)
392ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if got_fields:
393ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        settings.__getattribute__(group.name).CopyFrom(group_message)
394ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
395ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  def GatherUserPolicySettings(self, settings, policies):
39672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    '''Copies all the policies from a dictionary into a protobuf of type
39772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    CloudPolicySettings.
39872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
39972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Args:
40072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      settings: The destination: a CloudPolicySettings protobuf.
40172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      policies: The source: a dictionary containing policies under keys
40272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          'recommended' and 'mandatory'.
40372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    '''
40472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    for group in settings.DESCRIPTOR.fields:
40572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # Create protobuf message for group.
40672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      group_message = eval('cp.' + group.message_type.name + '()')
40772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # We assume that this policy group will be recommended, and only switch
40872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # it to mandatory if at least one of its members is mandatory.
40972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      group_message.policy_options.mode = cp.PolicyOptions.RECOMMENDED
41072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # Indicates if at least one field was set in |group_message|.
41172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      got_fields = False
41272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # Iterate over fields of the message and feed them from the
41372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      # policy config file.
41472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      for field in group_message.DESCRIPTOR.fields:
41572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        field_value = None
41672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        if field.name in policies['mandatory']:
41772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          group_message.policy_options.mode = cp.PolicyOptions.MANDATORY
41872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          field_value = policies['mandatory'][field.name]
41972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        elif field.name in policies['recommended']:
42072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          field_value = policies['recommended'][field.name]
42172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        if field_value != None:
42272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          got_fields = True
42372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen          self.SetProtobufMessageField(group_message, field, field_value)
42472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      if got_fields:
42572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen        settings.__getattribute__(group.name).CopyFrom(group_message)
42672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
427dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  def ProcessCloudPolicy(self, msg):
42872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    """Handles a cloud policy request. (New protocol for policy requests.)
42972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
43072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Checks for authorization, encodes the policy into protobuf representation,
43172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    signs it and constructs the repsonse.
43272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
43372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Args:
43472a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      msg: The CloudPolicyRequest message received from the client.
43572a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
43672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    Returns:
43772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen      A tuple of HTTP status code and response data to send to the client.
43872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    """
439dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
440dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    token_info, error = self.CheckToken()
441dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    if not token_info:
442dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      return error
44372a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
444ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    # Response is only given if the scope is specified in the config file.
445ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    # Normally 'google/chromeos/device' and 'google/chromeos/user' should be
446ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    # accepted.
447ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    policy_value = ''
448dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    if (msg.policy_type in token_info['allowed_policy_types'] and
449dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        msg.policy_type in self._server.policy):
450ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if msg.policy_type == 'google/chromeos/user':
451ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        settings = cp.CloudPolicySettings()
452ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        self.GatherUserPolicySettings(settings,
453ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                      self._server.policy[msg.policy_type])
454ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        policy_value = settings.SerializeToString()
455ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      elif msg.policy_type == 'google/chromeos/device':
456ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        settings = dp.ChromeDeviceSettingsProto()
457ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        self.GatherDevicePolicySettings(settings,
458ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                        self._server.policy[msg.policy_type])
459ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        policy_value = settings.SerializeToString()
460ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
461ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    # Figure out the key we want to use. If multiple keys are configured, the
462ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    # server will rotate through them in a round-robin fashion.
463ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    signing_key = None
464ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    req_key = None
465ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    key_version = 1
466ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    nkeys = len(self._server.keys)
467ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if msg.signature_type == dm.PolicyFetchRequest.SHA1_RSA and nkeys > 0:
468ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if msg.public_key_version in range(1, nkeys + 1):
469ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        # requested key exists, use for signing and rotate.
470ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        req_key = self._server.keys[msg.public_key_version - 1]['private_key']
471ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        key_version = (msg.public_key_version % nkeys) + 1
472ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      signing_key = self._server.keys[key_version - 1]
473ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
474ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    # Fill the policy data protobuf.
475dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    policy_data = dm.PolicyData()
476dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    policy_data.policy_type = msg.policy_type
477dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    policy_data.timestamp = int(time.time() * 1000)
478dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    policy_data.request_token = token_info['device_token'];
479ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    policy_data.policy_value = policy_value
480dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    policy_data.machine_name = token_info['machine_name']
481ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if signing_key:
482ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      policy_data.public_key_version = key_version
483ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    policy_data.username = self._server.username
484ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    policy_data.device_id = token_info['device_id']
485dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    signed_data = policy_data.SerializeToString()
48672a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
48772a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    response = dm.DeviceManagementResponse()
488dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    fetch_response = response.policy_response.response.add()
489dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    fetch_response.policy_data = signed_data
490ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if signing_key:
491ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      fetch_response.policy_data_signature = (
492ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          signing_key['private_key'].hashAndSign(signed_data).tostring())
493ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if msg.public_key_version != key_version:
494ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        fetch_response.new_public_key = signing_key['public_key']
495ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        if req_key:
496ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          fetch_response.new_public_key_signature = (
497ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen              req_key.hashAndSign(fetch_response.new_public_key).tostring())
49872a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
49972a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    self.DumpMessage('Response', response)
50072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
50172a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen    return (200, response.SerializeToString())
50272a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
5034a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def CheckToken(self):
5044a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Helper for checking whether the client supplied a valid DM token.
5054a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
5064a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Extracts the token from the request and passed to the server in order to
507dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    look up the client.
5084a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
5094a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
510dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      A pair of token information record and error response. If the first
511dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      element is None, then the second contains an error code to send back to
512dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      the client. Otherwise the first element is the same structure that is
513dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      returned by LookupToken().
5144a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
5154a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    error = None
516201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    dmtoken = None
517201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    request_device_id = self.GetUniqueParam('deviceid')
5184a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    match = re.match('GoogleDMToken token=(\\w+)',
5194a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch                     self._headers.getheader('Authorization', ''))
5204a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if match:
5214a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      dmtoken = match.group(1)
522201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    if not dmtoken:
523201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch      error = dm.DeviceManagementResponse.DEVICE_MANAGEMENT_TOKEN_INVALID
524201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch    else:
525dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      token_info = self._server.LookupToken(dmtoken)
526dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      if (not token_info or
527dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen          not request_device_id or
528dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen          token_info['device_id'] != request_device_id):
529dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        error = dm.DeviceManagementResponse.DEVICE_NOT_FOUND
530dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      else:
531dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen        return (token_info, None)
5324a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
5334a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response = dm.DeviceManagementResponse()
5344a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    response.error = error
5354a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
5364a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self.DumpMessage('Response', response)
5374a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
5384a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return (None, (200, response.SerializeToString()))
5394a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
5404a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def DumpMessage(self, label, msg):
5414a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Helper for logging an ASCII dump of a protobuf message."""
5424a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    logging.debug('%s\n%s' % (label, str(msg)))
5434a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
5444a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochclass TestServer(object):
5454a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  """Handles requests and keeps global service state."""
5464a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
547ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen  def __init__(self, policy_path, private_key_paths, policy_user):
5484a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Initializes the server.
5494a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
5504a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
5514a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      policy_path: Names the file to read JSON-formatted policy from.
552ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      private_key_paths: List of paths to read private keys from.
5534a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
554dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    self._registered_tokens = {}
5554a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    self.policy = {}
556ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
557ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    # There is no way to for the testserver to know the user name belonging to
558ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    # the GAIA auth token we received (short of actually talking to GAIA). To
559ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    # address this, we have a command line parameter to set the username that
560ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    # the server should report to the client.
561ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    self.username = policy_user
562ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
5634a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    if json is None:
5644a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      print 'No JSON module, cannot parse policy information'
5654a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    else :
5664a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      try:
5674a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        self.policy = json.loads(open(policy_path).read())
5684a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      except IOError:
5694a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch        print 'Failed to load policy from %s' % policy_path
5704a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
571ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    self.keys = []
572ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    if private_key_paths:
573ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      # Load specified keys from the filesystem.
574ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      for key_path in private_key_paths:
575ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        try:
576ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          key = tlslite.api.parsePEMKey(open(key_path).read(), private=True)
577ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        except IOError:
578ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          print 'Failed to load private key from %s' % key_path
579ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          continue
580ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
581ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        assert key != None
582ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        self.keys.append({ 'private_key' : key })
583ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    else:
584ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      # Generate a key if none were specified.
585ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      key = tlslite.api.generateRSAKey(1024)
586ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      assert key != None
587ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      self.keys.append({ 'private_key' : key })
588ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
589ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    # Derive the public keys from the loaded private keys.
590ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen    for entry in self.keys:
591ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      key = entry['private_key']
592ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
593ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      algorithm = asn1der.Sequence(
594ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen          [ asn1der.Data(asn1der.OBJECT_IDENTIFIER, PKCS1_RSA_OID),
595ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen            asn1der.Data(asn1der.NULL, '') ])
596ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      rsa_pubkey = asn1der.Sequence([ asn1der.Integer(key.n),
597ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen                                      asn1der.Integer(key.e) ])
598ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      pubkey = asn1der.Sequence([ algorithm, asn1der.Bitstring(rsa_pubkey) ])
599ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      entry['public_key'] = pubkey;
60072a454cd3513ac24fbdd0e0cb9ad70b86a99b801Kristian Monsen
6014a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def HandleRequest(self, path, headers, request):
6024a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Handles a request.
6034a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
6044a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
6054a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      path: The request path and query parameters received from the client.
6064a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      headers: A rfc822.Message-like object containing HTTP headers.
6074a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      request: The request data received from the client as a string.
6084a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
6094a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      A pair of HTTP status code and response data to send to the client.
6104a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
6114a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    handler = RequestHandler(self, path, headers, request)
6124a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    return handler.HandleRequest()
6134a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
614dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  def RegisterDevice(self, device_id, machine_id, type):
615dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    """Registers a device or user and generates a DM token for it.
6164a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
6174a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
6184a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      device_id: The device identifier provided by the client.
6194a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
6204a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
6214a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      The newly generated device token for the device.
6224a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
6234a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    dmtoken_chars = []
6244a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    while len(dmtoken_chars) < 32:
6254a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      dmtoken_chars.append(random.choice('0123456789abcdef'))
626dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    dmtoken = ''.join(dmtoken_chars)
627dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    allowed_policy_types = {
628dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      dm.DeviceRegisterRequest.USER: ['google/chromeos/user'],
629dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      dm.DeviceRegisterRequest.DEVICE: ['google/chromeos/device'],
630dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      dm.DeviceRegisterRequest.TT: ['google/chromeos/user'],
631dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    }
632dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    self._registered_tokens[dmtoken] = {
633dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      'device_id': device_id,
634dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      'device_token': dmtoken,
635dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      'allowed_policy_types': allowed_policy_types[type],
636dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      'machine_name': 'chromeos-' + machine_id,
637dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    }
638dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    return self._registered_tokens[dmtoken]
639dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen
640dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen  def LookupToken(self, dmtoken):
641dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    """Looks up a device or a user by DM token.
6424a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
6434a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
6444a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      dmtoken: The device management token provided by the client.
6454a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
6464a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Returns:
647dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      A dictionary with information about a device or user that is registered by
648dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      dmtoken, or None if the token is not found.
6494a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
650dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    return self._registered_tokens.get(dmtoken, None)
6514a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
6524a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch  def UnregisterDevice(self, dmtoken):
6534a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """Unregisters a device identified by the given DM token.
6544a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch
6554a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    Args:
6564a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch      dmtoken: The device management token provided by the client.
6574a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch    """
658dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen    if dmtoken in self._registered_tokens.keys():
659dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen      del self._registered_tokens[dmtoken]
660