Index: components/cloud_devices/tools/prototype/prototype.py |
diff --git a/components/cloud_devices/tools/prototype/prototype.py b/components/cloud_devices/tools/prototype/prototype.py |
index 2a0b9cdac7e5ef20048674277943418eb811fa27..aa8d7e5d6c3330fae88a6e92e27da44a3859de57 100755 |
--- a/components/cloud_devices/tools/prototype/prototype.py |
+++ b/components/cloud_devices/tools/prototype/prototype.py |
@@ -39,9 +39,9 @@ _API_CLIENT_FILE = 'config.json' |
_API_DISCOVERY_FILE = 'discovery.json' |
_DEVICE_STATE_FILE = 'device_state.json' |
-_DEVICE_SETUP_SSID = "GCDPrototype.camera.privet" |
-_DEVICE_NAME = "GCD Prototype" |
-_DEVICE_TYPE = "camera" |
+_DEVICE_SETUP_SSID = 'GCDPrototype.camera.privet' |
+_DEVICE_NAME = 'GCD Prototype' |
+_DEVICE_TYPE = 'camera' |
_DEVICE_PORT = 8080 |
DEVICE_DRAFT = { |
@@ -388,6 +388,7 @@ class MDnsWrapper(object): |
self.run_command() |
def get_command(self): |
+ """Return the command to run mDNS daemon.""" |
cmd = [ |
'avahi-publish', |
'-s', '--subtype=_%s._sub._privet._tcp' % _DEVICE_TYPE, |
@@ -490,11 +491,11 @@ class CloudDevice(object): |
'oauthClientId': self.oauth_client_id |
} |
- self.gcd.registrationTickets().patch(registration_ticke_id=token, |
+ self.gcd.registrationTickets().patch(registrationTicketId=token, |
body=resource).execute() |
final_ticket = self.gcd.registrationTickets().finalize( |
- registration_ticke_id=token).execute() |
+ registrationTicketId=token).execute() |
authorization_code = final_ticket['robotAccountAuthorizationCode'] |
flow = OAuth2WebServerFlow(self.oauth_client_id, self.oauth_secret, |
@@ -599,25 +600,6 @@ def post_provisioning(f): |
return inner |
-def extract_encryption_params(f): |
- """Extracts privet encription header and pass as parameter into function.""" |
- def inner(self, request, response_func, *args): |
- """Extracts privet encription header.""" |
- try: |
- client_id = request.headers['X-Privet-Client-ID'] |
- if 'X-Privet-Encrypted' in request.headers: |
- encrypted = (request.headers['X-Privet-Encrypted'].lower() == 'true') |
- else: |
- encrypted = False |
- except (KeyError, TypeError): |
- print 'Missing client parameters in headers' |
- response_func(400, {'error': 'missing_client_parameters'}) |
- return True |
- |
- return f(self, request, response_func, client_id, encrypted, *args) |
- return inner |
- |
- |
class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): |
"""Handles HTTP requests.""" |
@@ -647,8 +629,8 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): |
class DummySession(object): |
"""Handles sessions.""" |
- def __init__(self, client_id): |
- self.client_id = client_id |
+ def __init__(self, session_id): |
+ self.session_id = session_id |
self.key = None |
def do_step(self, step, package): |
@@ -663,12 +645,42 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): |
def encrypt(self, plain_data): |
return self.key + json.dumps(plain_data) |
- def get_client_id(self): |
- return self.client_id |
+ def get_session_id(self): |
+ return self.session_id |
def get_stype(self): |
return 'dummy' |
+ def get_status(self): |
+ return 'complete' |
+ |
+ class EmptySession(object): |
+ """Handles sessions.""" |
+ |
+ def __init__(self, session_id): |
+ self.session_id = session_id |
+ self.key = None |
+ |
+ def do_step(self, step, package): |
+ if step != 0 or package != '': |
+ raise self.InvalidStepError() |
+ return '' |
+ |
+ def decrypt(self, cyphertext): |
+ return json.loads(cyphertext) |
+ |
+ def encrypt(self, plain_data): |
+ return json.dumps(plain_data) |
+ |
+ def get_session_id(self): |
+ return self.session_id |
+ |
+ def get_stype(self): |
+ return 'empty' |
+ |
+ def get_status(self): |
+ return 'complete' |
+ |
def __init__(self, ioloop, state): |
if os.path.exists('on_real_device'): |
mdns_wrappers = CommandWrapperReal |
@@ -694,7 +706,7 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): |
'/deprecated/wifi/switch': self.do_wifi_switch, |
'/privet/v2/session/handshake': self.do_session_handshake, |
'/privet/v2/session/cancel': self.do_session_cancel, |
- '/privet/v2/session/api': self.do_session_api, |
+ '/privet/v2/session/call': self.do_session_call, |
'/privet/v2/setup/start': |
self.get_insecure_api_handler(self.do_secure_setup_start), |
'/privet/v2/setup/cancel': |
@@ -706,7 +718,8 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): |
self.current_session = None |
self.session_cancel_callback = None |
self.session_handlers = { |
- 'dummy': self.DummySession |
+ 'dummy': self.DummySession, |
+ 'empty': self.EmptySession |
} |
self.secure_handlers = { |
@@ -732,24 +745,24 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): |
@get_only |
def do_ping(self, unused_request, response_func): |
- response_func(200, '{ "pong": true }') |
+ response_func(200, {'pong': True}) |
return True |
@get_only |
- def do_public_info(self, request, unused_response_func): |
+ def do_public_info(self, unused_request, response_func): |
info = dict(self.get_common_info().items() + { |
'stype': self.session_handlers.keys()}.items()) |
- self.real_send_response(request, 200, info) |
+ response_func(200, info) |
@post_provisioning |
@get_only |
- def do_info(self, request, unused_response_func): |
+ def do_info(self, unused_request, response_func): |
specific_info = { |
'x-privet-token': 'sample', |
'api': sorted(self.handlers.keys()) |
} |
info = dict(self.get_common_info().items() + specific_info.items()) |
- self.real_send_response(request, 200, info) |
+ response_func(200, info) |
return True |
@post_only |
@@ -762,7 +775,7 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): |
passw = data['passw'] |
except KeyError: |
print 'Malformed content: ' + repr(data) |
- self.real_send_response(request, 400, {'error': 'invalid_params'}) |
+ response_func(400, {'error': 'invalidParams'}) |
traceback.print_exc() |
return True |
@@ -771,107 +784,116 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): |
# TODO(noamsml): Return to normal wifi after timeout (cancelable) |
return True |
- @extract_encryption_params |
@post_only |
- @wifi_provisioning |
- def do_session_handshake(self, request, unused_response_func, client_id, |
- unused_encrypted): |
+ def do_session_handshake(self, request, response_func): |
"""Handles /privet/v2/session/handshake requests.""" |
+ |
data = json.loads(request.body) |
try: |
- stype = data['stype'] |
+ stype = data['keyExchangeType'] |
step = data['step'] |
package = base64.b64decode(data['package']) |
+ session_id = data['sessionID'] |
except (KeyError, TypeError): |
traceback.print_exc() |
print 'Malformed content: ' + repr(data) |
- self.real_send_response(request, 400, {'error': 'invalid_params'}) |
+ response_func(400, {'error': 'invalidParams'}) |
return True |
if self.current_session: |
- if client_id != self.current_session.get_client_id(): |
- self.real_send_response(request, 500, {'error': 'in_session'}) |
+ if session_id != self.current_session.get_session_id(): |
+ response_func(400, {'error': 'maxSessionsExceeded'}) |
return True |
if stype != self.current_session.get_stype(): |
- self.real_send_response(request, 500, {'error': 'invalid_stype'}) |
+ response_func(400, {'error': 'unsupportedKeyExchangeType'}) |
return True |
else: |
if stype not in self.session_handlers: |
- self.real_send_response(request, 500, {'error': 'invalid_stype'}) |
+ response_func(400, {'error': 'unsupportedKeyExchangeType'}) |
return True |
- self.current_session = self.session_handlers[stype](client_id) |
+ self.current_session = self.session_handlers[stype](session_id) |
try: |
output_package = self.current_session.do_step(step, package) |
except self.InvalidStepError: |
- self.real_send_response(request, 500, {'error': 'invalid_step'}) |
+ response_func(400, {'error': 'invalidStep'}) |
return True |
except self.InvalidPackageError: |
- self.real_send_response(request, 500, {'error': 'invalid_step'}) |
+ response_func(400, {'error': 'invalidPackage'}) |
return True |
return_obj = { |
- 'stype': stype, |
+ 'status': self.current_session.get_status(), |
'step': step, |
- 'package': base64.b64encode(output_package) |
+ 'package': base64.b64encode(output_package), |
+ 'sessionID': session_id |
} |
- self.real_send_response(request, 200, return_obj) |
+ response_func(200, return_obj) |
self.post_session_cancel() |
return True |
- @extract_encryption_params |
@post_only |
- @wifi_provisioning |
- def do_session_cancel(self, request, unused_response_func, client_id, |
- unused_encrypted): |
- if client_id == self.current_session.client_id: |
+ def do_session_cancel(self, request, response_func): |
+ """Handles /privet/v2/session/cancel requests.""" |
+ data = json.loads(request.body) |
+ try: |
+ session_id = data['sessionID'] |
+ except KeyError: |
+ response_func(400, {'error': 'invalidParams'}) |
+ return True |
+ |
+ if self.current_session and session_id == self.current_session.session_id: |
self.current_session = None |
if self.session_cancel_callback: |
self.session_cancel_callback.cancel() |
+ response_func(200, {'status': 'cancelled', 'sessionID': session_id}) |
else: |
- self.real_send_response(request, 400, {'error': 'invalid_client_id'}) |
+ response_func(400, {'error': 'unknownSession'}) |
return True |
- @extract_encryption_params |
@post_only |
- @wifi_provisioning |
- def do_session_api(self, request, response_func, client_id, encrypted): |
- """Handles /privet/v2/session/api requests.""" |
- if not encrypted: |
- response_func(400, {'error': 'encryption_required'}) |
+ def do_session_call(self, request, response_func): |
+ """Handles /privet/v2/session/call requests.""" |
+ try: |
+ session_id = request.headers['X-Privet-SessionID'] |
+ except KeyError: |
+ response_func(400, {'error': 'unknownSession'}) |
return True |
- if not self.current_session or client_id != self.current_session.client_id: |
- response_func(405, {'error': 'invalid_client_id'}) |
+ if (not self.current_session or |
+ session_id != self.current_session.session_id): |
+ response_func(400, {'error': 'unknownSession'}) |
return True |
try: |
decrypted = self.current_session.decrypt(request.body) |
except self.EncryptionError: |
- response_func(415, {'error': 'decryption_failed'}) |
+ response_func(400, {'error': 'encryptionError'}) |
return True |
def encrypted_response_func(code, data): |
if 'error' in data: |
- self.encrypted_send_response(request, code, data) |
+ self.encrypted_send_response(request, code, dict(data.items() + { |
+ 'api': decrypted['api'] |
+ }.items())) |
else: |
self.encrypted_send_response(request, code, { |
'api': decrypted['api'], |
- 'response': data |
+ 'output': data |
}) |
- if ('api' not in decrypted or 'request' not in decrypted or |
- type(decrypted['request']) != dict): |
+ if ('api' not in decrypted or 'input' not in decrypted or |
+ type(decrypted['input']) != dict): |
print 'Invalid params in API stage' |
- encrypted_response_func(400, {'error': 'invalid_params'}) |
+ encrypted_response_func(200, {'error': 'invalidParams'}) |
return True |
if decrypted['api'] in self.secure_handlers: |
self.secure_handlers[decrypted['api']](request, |
encrypted_response_func, |
- decrypted['request']) |
+ decrypted['input']) |
else: |
- encrypted_response_func(400, {'error': 'unknown_api'}) |
+ encrypted_response_func(200, {'error': 'unknownApi'}) |
self.post_session_cancel() |
return True |
@@ -925,16 +947,20 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): |
token = params['registration']['ticketID'] |
except KeyError: |
print 'Invalid params in bootstrap stage' |
- response_func(400, {'error': 'invalid_params'}) |
+ response_func(400, {'error': 'invalidParams'}) |
return |
- if has_wifi: |
- self.wifi_handler.switch_to_wifi(ssid, passw, token) |
- elif token: |
- self.cloud_device.register(token) |
- else: |
- response_func(400, {'error': 'invalid_params'}) |
- return |
+ try: |
+ if has_wifi: |
+ self.wifi_handler.switch_to_wifi(ssid, passw, token) |
+ elif token: |
+ self.cloud_device.register(token) |
+ else: |
+ response_func(400, {'error': 'invalidParams'}) |
+ return |
+ except HttpError: |
+ pass # TODO(noamsml): store error message in this case |
+ |
self.do_secure_status(unused_request, response_func, params) |
def do_secure_setup_cancel(self, request, response_func, params): |
@@ -945,23 +971,27 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): |
self.real_send_response(request, code, data) |
handled = False |
+ print '[INFO] %s %s' % (request.method, request.path) |
if request.path in self.handlers: |
handled = self.handlers[request.path](request, response_func) |
if not handled: |
- self.real_send_response(request, 404, {'error': 'Not found'}) |
+ self.real_send_response(request, 404, {'error': 'notFound'}) |
def encrypted_send_response(self, request, code, data): |
- self.real_send_response(request, code, |
- self.current_session.encrypt(data)) |
+ self.raw_send_response(request, code, |
+ self.current_session.encrypt(data)) |
def real_send_response(self, request, code, data): |
data = json.dumps(data, sort_keys=True, indent=2, separators=(',', ': ')) |
+ data += '\n' |
+ self.raw_send_response(request, code, data) |
+ |
+ def raw_send_response(self, request, code, data): |
request.write('HTTP/1.1 %d Maybe OK\n' % code) |
request.write('Content-Type: application/json\n') |
- request.write('Content-Length: %d\n' % len(data)) |
- write_data = '\n%s' % data |
- request.write(str(write_data)) |
+ request.write('Content-Length: %s\n\n' % len(data)) |
+ request.write(data) |
request.finish() |
def device_state(self): |