blob: 1716d4a223d2e14b39666469ee646c417a05c50d [file] [log] [blame]
# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Module contains a simple implementation of the registrationTickets RPC."""
import logging
from cherrypy import tools
import time
import uuid
import common
from fake_device_server import common_util
from fake_device_server import server_errors
class RegistrationTickets(object):
"""A simple implementation of the registrationTickets interface.
A common workflow of using this API is:
POST .../ # Creates a new ticket with id <id>.
PATCH .../<id> with json blob # Updates ticket with device info including
OAUTH2 bearer token + userEmail: me to claim the device.
POST .../<id>/finalize # Finalize the device registration (robot info).
"""
# OAUTH2 Bearer Access Token
TEST_ACCESS_TOKEN = '1/TEST-ME'
# Needed for cherrypy to expose this to requests.
exposed = True
def __init__(self, resource, devices_instance):
"""Initializes a registration ticket.
@param resource: A resource delegate.
"""
self.resource = resource
self.devices_instance = devices_instance
def _default_registration_ticket(self):
"""Creates and returns a new registration ticket."""
current_time_ms = time.time() * 1000
ticket = {'kind': 'clouddevices#registrationTicket',
'creationTimeMs': current_time_ms,
'expirationTimeMs': current_time_ms + (10 * 1000)}
return ticket
def _finalize(self, id, api_key, ticket):
"""Finalizes the ticket by adding robot account info.
Raises:
server_errors.HTTPError if the ticket hasn't been claimed yet.
"""
if 'userEmail' not in ticket:
raise server_errors.HTTPError(400, 'Unclaimed ticket')
robot_account_email = 'robot@test.org'
robot_auth = uuid.uuid4().hex
new_data = {'robotAccountEmail': robot_account_email,
'robotAccountAuthorizationCode':robot_auth}
updated_data_val = self.resource.update_data_val(id, api_key, new_data)
return self.devices_instance.create_device(api_key, updated_data_val)
def _add_claim_data(self, data):
"""Adds userEmail to |data| to claim ticket.
Raises:
server_errors.HTTPError if there is an authorization error.
"""
access_token = common_util.grab_header_field('Authorization')
if not access_token:
raise server_errors.HTTPError(401, 'Missing Authorization.')
# Authorization should contain "<type> <token>"
access_token_list = access_token.split()
if len(access_token_list) != 2:
raise server_errors.HTTPError(400, 'Malformed Authorization field')
[type, code] = access_token_list
# TODO(sosa): Consider adding HTTP WWW-Authenticate response header
# field
if type != 'Bearer':
raise server_errors.HTTPError(403, 'Authorization requires '
'bearer token.')
elif code != RegistrationTickets.TEST_ACCESS_TOKEN:
raise server_errors.HTTPError(403, 'Wrong access token.')
else:
logging.info('Ticket is being claimed.')
data['userEmail'] = 'test_account@chromium.org'
@tools.json_out()
def GET(self, *args, **kwargs):
"""GET .../ticket_number returns info about the ticket.
Raises:
server_errors.HTTPError if the ticket doesn't exist.
"""
id, api_key, _ = common_util.parse_common_args(args, kwargs)
return self.resource.get_data_val(id, api_key)
@tools.json_out()
def POST(self, *args, **kwargs):
"""Either creates a ticket OR claim/finalizes a ticket.
This method implements the majority of the registration workflow.
More specifically:
POST ... creates a new ticket
POST .../ticket_number/claim claims a given ticket with a fake email.
POST .../ticket_number/finalize finalizes a ticket with a robot account.
Raises:
server_errors.HTTPError if the ticket should exist but doesn't
(claim/finalize) or if we can't parse all the args.
"""
id, api_key, operation = common_util.parse_common_args(
args, kwargs, supported_operations=set(['finalize']))
if operation:
ticket = self.resource.get_data_val(id, api_key)
if operation == 'finalize':
return self._finalize(id, api_key, ticket)
else:
raise server_errors.HTTPError(
400, 'Unsupported method call %s' % operation)
else:
data = common_util.parse_serialized_json()
if not data:
data = {}
# We have an insert operation so make sure we have all required
# fields.
if not id:
data.update(self._default_registration_ticket())
return self.resource.update_data_val(id, api_key, data_in=data)
@tools.json_out()
def PATCH(self, *args, **kwargs):
"""Updates the given ticket with the incoming json blob.
Format of this call is:
PATCH .../ticket_number
Caller must define a json blob to patch the ticket with.
Raises:
server_errors.HTTPError if the ticket doesn't exist.
"""
id, api_key, _ = common_util.parse_common_args(args, kwargs)
if not id:
server_errors.HTTPError(400, 'Missing id for operation')
data = common_util.parse_serialized_json()
# Handle claiming a ticket with an authorized request.
if data and data.get('userEmail') == 'me':
self._add_claim_data(data)
return self.resource.update_data_val(
id, api_key, data_in=data)
@tools.json_out()
def PUT(self, *args, **kwargs):
"""Replaces the given ticket with the incoming json blob.
Format of this call is:
PUT .../ticket_number
Caller must define a json blob to patch the ticket with.
Raises:
"""
id, api_key, _ = common_util.parse_common_args(args, kwargs)
if not id:
server_errors.HTTPError(400, 'Missing id for operation')
data = common_util.parse_serialized_json()
# Handle claiming a ticket with an authorized request.
if data and data.get('userEmail') == 'me':
self._add_claim_data(data)
return self.resource.update_data_val(
id, api_key, data_in=data, update=False)