From 08619244f0a46ba8688e122e0daca64a4094c196 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Fri, 31 Mar 2017 21:02:09 -0700 Subject: [PATCH] Generate a unique token for each mobile device --- data/interfaces/default/settings.html | 81 +++++++++++++++------------ plexpy/__init__.py | 2 + plexpy/api2.py | 39 ++++++------- plexpy/mobile_app.py | 51 +++++++++++++++-- plexpy/notifiers.py | 43 ++++++-------- plexpy/webserve.py | 17 +++++- 6 files changed, 139 insertions(+), 94 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index bf3b76fb..166999cc 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -2652,13 +2652,9 @@ $(document).ready(function() { $('#api_key').click(function(){ $('#api_key').select() }); $("#generate_api").click(function() { - $.get('generateAPI', - function(data){ - if (data.error != undefined) { - alert(data.error); - return; - } - $('#api_key').val(data); + $.get('generate_api_key', + function (apikey) { + $('#api_key').val(apikey); }); }); @@ -3046,41 +3042,52 @@ $(document).ready(function() { }); $('#api_qr_https').toggle(!(url.startsWith('https'))); - var token = Math.random().toString(36).substr(2, 20); - var encoded_string = url + '|' + $('#api_key').val() + '|' + token; - $('#api_qr_string').html(encoded_string); - $('#api_qr_code').empty().qrcode({ - text: encoded_string - }); + $.get('generate_api_key', { device: true }).then(function (token) { + var encoded_string = url + '|' + token; + $('#api_qr_string').html(encoded_string); + $('#api_qr_code').empty().qrcode({ + text: encoded_string + }); - (function poll(){ - verifiedDevice = false; - setTimeout(function() { - $.ajax({ - url: 'verify_mobile_device', - type: 'GET', - data: { device_token: token }, - success: function(data) { - if (data.result === 'success') { - verifiedDevice = true; - getMobileDevicesTable(); - $('#api-qr-modal').modal('hide'); - showMsg(' ' + data.message, false, true, 5000, false); - } - }, - complete: function() { - if (!(verifiedDevice)) { - poll(); - } - }, - timeout: 1000 - }); - }, 1000); - })(); + (function poll(){ + verifiedDevice = false; + setTimeout(function() { + $.ajax({ + url: 'verify_mobile_device', + type: 'GET', + data: { device_token: token }, + success: function(data) { + if (data.result === 'success') { + verifiedDevice = true; + getMobileDevicesTable(); + $('#api-qr-modal').modal('hide'); + showMsg(' ' + data.message, false, true, 5000, false); + } + }, + complete: function() { + if (!(verifiedDevice)) { + poll(); + } + }, + timeout: 1000 + }); + }, 1000); + })(); + }); }); }); $('#api-qr-modal').on('hidden.bs.modal', function () { + if (!(verifiedDevice)) { + $.ajax({ + url: 'verify_mobile_device', + type: 'GET', + data: { cancel: true }, + success: function(data) { + showMsg(' ' + data.message, false, true, 5000, false); + } + }); + } verifiedDevice = true; }) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 3ed46c55..78d0436c 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -36,6 +36,7 @@ import activity_pinger import config import database import logger +import mobile_app import notification_handler import notifiers import plextv @@ -165,6 +166,7 @@ def initialize(config_file): # Add notifier configs to logger blacklist notifiers.blacklist_logger() + mobile_app.blacklist_logger() # Check if PlexPy has a uuid if CONFIG.PMS_UUID == '' or not CONFIG.PMS_UUID: diff --git a/plexpy/api2.py b/plexpy/api2.py index 82d493c2..40d9a3e9 100644 --- a/plexpy/api2.py +++ b/plexpy/api2.py @@ -33,6 +33,7 @@ import plexpy import config import database import logger +import mobile_app import plextv import pmsconnect @@ -88,7 +89,9 @@ class API2: elif 'apikey' not in kwargs: self._api_msg = 'Parameter apikey is required' - elif kwargs.get('apikey', '') != plexpy.CONFIG.API_KEY: + elif (kwargs.get('apikey', '') != plexpy.CONFIG.API_KEY and + kwargs.get('apikey', '') != mobile_app.TEMP_DEVICE_TOKEN and + not mobile_app.get_mobile_device_by_token(kwargs.get('apikey', ''))): self._api_msg = 'Invalid apikey' elif 'cmd' not in kwargs: @@ -105,7 +108,9 @@ class API2: # Allow override for the api. self._api_out_type = kwargs.pop('out_type', 'json') - if self._api_apikey == plexpy.CONFIG.API_KEY and plexpy.CONFIG.API_ENABLED and self._api_cmd in self._api_valid_methods: + if ((self._api_apikey == plexpy.CONFIG.API_KEY or + mobile_app.get_mobile_device_by_token(self._api_apikey)) and + plexpy.CONFIG.API_ENABLED and self._api_cmd in self._api_valid_methods): self._api_authenticated = True self._api_msg = None self._api_kwargs = kwargs @@ -341,7 +346,7 @@ class API2: return data - def register_device(self, device_id='', device_name='', device_token='', **kwargs): + def register_device(self, device_id='', device_name='', **kwargs): """ Registers the PlexPy Android App for notifications. ``` @@ -350,7 +355,7 @@ class API2: device_id (str): The OneSignal device id of the PlexPy Android App Optional parameters: - device_token (str): The device token to verify QR code scan + None Returns: None @@ -366,28 +371,18 @@ class API2: self._api_result_type = 'error' return + result = mobile_app.add_mobile_device(device_id=device_id, + device_name=device_name, + device_token=self._api_apikey) - db = database.MonitorDatabase() - - keys = {'device_id': device_id} - values = {'device_name': device_name, - 'device_token': device_token} - - try: - result = db.upsert(table_name='mobile_devices', key_dict=keys, value_dict=values) - except Exception as e: - logger.warn(u"PlexPy APIv2 :: Failed to register mobile device in the database: %s." % e) + if result: + self._api_msg = 'Device registration successful.' + self._api_result_type = 'success' + mobile_app.TEMP_DEVICE_TOKEN = None + else: self._api_msg = 'Device registartion failed: database error.' self._api_result_type = 'error' - return - if result == 'insert': - logger.info(u"PlexPy APIv2 :: Registered mobile device in the database: %s." % device_name) - else: - logger.debug(u"PlexPy APIv2 :: Re-registered mobile device in the database: %s." % device_name) - - self._api_msg = 'Device registration successful.' - self._api_result_type = 'success' return def _api_make_md(self): diff --git a/plexpy/mobile_app.py b/plexpy/mobile_app.py index 575e2c09..f5bcb885 100644 --- a/plexpy/mobile_app.py +++ b/plexpy/mobile_app.py @@ -19,6 +19,9 @@ import helpers import logger +TEMP_DEVICE_TOKEN = None + + def get_mobile_devices(device_id=None, device_token=None): where = where_id = where_token = '' args = [] @@ -33,19 +36,57 @@ def get_mobile_devices(device_id=None, device_token=None): args.append(device_token) where += ' AND '.join([w for w in [where_id, where_token] if w]) - monitor_db = database.MonitorDatabase() - result = monitor_db.select('SELECT * FROM mobile_devices %s' % where, args=args) + db = database.MonitorDatabase() + result = db.select('SELECT * FROM mobile_devices %s' % where, args=args) return result +def get_mobile_device_by_token(device_token=None): + if not device_token: + return None + + return get_mobile_devices(device_token=device_token) + + +def add_mobile_device(device_id=None, device_name=None, device_token=None): + db = database.MonitorDatabase() + + keys = {'device_id': device_id} + values = {'device_name': device_name, + 'device_token': device_token} + + try: + result = db.upsert(table_name='mobile_devices', key_dict=keys, value_dict=values) + except Exception as e: + logger.warn(u"PlexPy MobileApp :: Failed to register mobile device in the database: %s." % e) + return + + if result == 'insert': + logger.info(u"PlexPy MobileApp :: Registered mobile device '%s' in the database." % device_name) + else: + logger.debug(u"PlexPy MobileApp :: Re-registered mobile device '%s' in the database." % device_name) + + return True + + def delete_mobile_device(device_id=None): - monitor_db = database.MonitorDatabase() + db = database.MonitorDatabase() if device_id: - logger.debug(u"PlexPy Notifiers :: Deleting device_id %s from the database." % device_id) - result = monitor_db.action('DELETE FROM mobile_devices WHERE device_id = ?', args=[device_id]) + logger.debug(u"PlexPy MobileApp :: Deleting device_id %s from the database." % device_id) + result = db.action('DELETE FROM mobile_devices WHERE device_id = ?', args=[device_id]) return True else: return False + +def blacklist_logger(): + devices = get_mobile_devices() + + blacklist = [] + + for d in devices: + blacklist.append(d['device_token']) + + logger._BLACKLIST_WORDS.extend(blacklist) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 47000732..7b03f68f 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -375,8 +375,8 @@ def get_notifiers(notifier_id=None, notify_action=None): args.append(1) where += ' AND '.join([w for w in [where_id, where_action] if w]) - monitor_db = database.MonitorDatabase() - result = monitor_db.select('SELECT id, agent_id, agent_name, agent_label, friendly_name, %s FROM notifiers %s' + db = database.MonitorDatabase() + result = db.select('SELECT id, agent_id, agent_name, agent_label, friendly_name, %s FROM notifiers %s' % (', '.join(notify_actions), where), args=args) for item in result: @@ -386,11 +386,11 @@ def get_notifiers(notifier_id=None, notify_action=None): def delete_notifier(notifier_id=None): - monitor_db = database.MonitorDatabase() + db = database.MonitorDatabase() if str(notifier_id).isdigit(): logger.debug(u"PlexPy Notifiers :: Deleting notifier_id %s from the database." % notifier_id) - result = monitor_db.action('DELETE FROM notifiers WHERE id = ?', + result = db.action('DELETE FROM notifiers WHERE id = ?', args=[notifier_id]) return True else: @@ -404,8 +404,8 @@ def get_notifier_config(notifier_id=None): logger.error(u"PlexPy Notifiers :: Unable to retrieve notifier config: invalid notifier_id %s." % notifier_id) return None - monitor_db = database.MonitorDatabase() - result = monitor_db.select_single('SELECT * FROM notifiers WHERE id = ?', + db = database.MonitorDatabase() + result = db.select_single('SELECT * FROM notifiers WHERE id = ?', args=[notifier_id]) if not result: @@ -466,10 +466,10 @@ def add_notifier_config(agent_id=None, **kwargs): values[a['name'] + '_subject'] = a['subject'] values[a['name'] + '_body'] = a['body'] - monitor_db = database.MonitorDatabase() + db = database.MonitorDatabase() try: - monitor_db.upsert(table_name='notifiers', key_dict=keys, value_dict=values) - notifier_id = monitor_db.last_insert_id() + db.upsert(table_name='notifiers', key_dict=keys, value_dict=values) + notifier_id = db.last_insert_id() logger.info(u"PlexPy Notifiers :: Added new notification agent: %s (notifier_id %s)." % (agent['label'], notifier_id)) return notifier_id except Exception as e: @@ -514,9 +514,9 @@ def set_notifier_config(notifier_id=None, agent_id=None, **kwargs): values.update(subject_text) values.update(body_text) - monitor_db = database.MonitorDatabase() + db = database.MonitorDatabase() try: - monitor_db.upsert(table_name='notifiers', key_dict=keys, value_dict=values) + db.upsert(table_name='notifiers', key_dict=keys, value_dict=values) logger.info(u"PlexPy Notifiers :: Updated notification agent: %s (notifier_id %s)." % (agent['label'], notifier_id)) return True except Exception as e: @@ -538,8 +538,8 @@ def send_notification(notifier_id=None, subject='', body='', notify_action='', * def blacklist_logger(): - monitor_db = database.MonitorDatabase() - notifiers = monitor_db.select('SELECT notifier_config FROM notifiers') + db = database.MonitorDatabase() + notifiers = db.select('SELECT notifier_config FROM notifiers') blacklist = [] blacklist_keys = [w.lstrip('_') for w in _BLACKLIST_KEYS] @@ -554,17 +554,6 @@ def blacklist_logger(): logger._BLACKLIST_WORDS.extend(blacklist) -def delete_mobile_device(device_id=None): - monitor_db = database.MonitorDatabase() - - if device_id: - logger.debug(u"PlexPy Notifiers :: Deleting device_id %s from the database." % device_id) - result = monitor_db.action('DELETE FROM mobile_devices WHERE device_id = ?', [device_id]) - return True - else: - return False - - class PrettyMetadata(object): def __init__(self, parameters): self.parameters = parameters @@ -692,7 +681,7 @@ class ANDROIDAPP(Notifier): return # Check mobile device is still registered - if self.config['device_id'] and not mobile_app.get_mobile_devices(device_id=self.config['device_id']): + if not mobile_app.get_mobile_devices(device_id=self.config['device_id']): logger.warn(u"PlexPy Notifiers :: Unable to send Android app notification: device not registered.") return @@ -867,8 +856,8 @@ class BROWSER(Notifier): if not self.config['enabled']: return - monitor_db = database.MonitorDatabase() - result = monitor_db.select('SELECT subject_text, body_text FROM notify_log ' + db = database.MonitorDatabase() + result = db.select('SELECT subject_text, body_text FROM notify_log ' 'WHERE agent_id = 17 AND timestamp >= ? ', args=[time.time() - 3]) diff --git a/plexpy/webserve.py b/plexpy/webserve.py index bb5e7227..5cd3379f 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -3204,9 +3204,14 @@ class WebInterface(object): @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin")) - def verify_mobile_device(self, device_token='', **kwargs): - result = mobile_app.get_mobile_devices(device_token=device_token) + def verify_mobile_device(self, device_token='', cancel=False, **kwargs): + if cancel == 'true': + mobile_app.TEMP_DEVICE_TOKEN = None + return {'result': 'error', 'message': 'Device registration cancelled.'} + + result = mobile_app.get_mobile_device_by_token(device_token) if result: + mobile_app.TEMP_DEVICE_TOKEN = None return {'result': 'success', 'message': 'Device registered successfully.', 'data': result} else: return {'result': 'error', 'message': 'Device not registered.'} @@ -3421,10 +3426,16 @@ class WebInterface(object): logger.warn(u"Unable to retrieve data for get_server_pref.") @cherrypy.expose + @cherrypy.tools.json_out() @requireAuth(member_of("admin")) - def generateAPI(self, **kwargs): + def generate_api_key(self, device=None, **kwargs): apikey = hashlib.sha224(str(random.getrandbits(256))).hexdigest()[0:32] logger.info(u"New API key generated.") + logger._BLACKLIST_WORDS.append(apikey) + + if device == 'true': + mobile_app.TEMP_DEVICE_TOKEN = apikey + return apikey @cherrypy.expose