diff --git a/plexpy/__init__.py b/plexpy/__init__.py index a0767371..bf47f57b 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -723,7 +723,8 @@ def dbcheck(): c_db.execute( 'CREATE TABLE IF NOT EXISTS user_login (id INTEGER PRIMARY KEY AUTOINCREMENT, ' 'timestamp INTEGER, user_id INTEGER, user TEXT, user_group TEXT, ' - 'ip_address TEXT, host TEXT, user_agent TEXT, success INTEGER DEFAULT 1)' + 'ip_address TEXT, host TEXT, user_agent TEXT, success INTEGER DEFAULT 1,' + 'expiry TEXT, jwt_token TEXT)' ) # notifiers table :: This table keeps record of the notification agent settings @@ -2300,6 +2301,18 @@ def dbcheck(): 'ALTER TABLE user_login ADD COLUMN success INTEGER DEFAULT 1' ) + # Upgrade user_login table from earlier versions + try: + c_db.execute('SELECT expiry FROM user_login') + except sqlite3.OperationalError: + logger.debug("Altering database. Updating database table user_login.") + c_db.execute( + 'ALTER TABLE user_login ADD COLUMN expiry TEXT' + ) + c_db.execute( + 'ALTER TABLE user_login ADD COLUMN jwt_token TEXT' + ) + # Rename notifiers in the database result = c_db.execute('SELECT agent_label FROM notifiers ' 'WHERE agent_label = "XBMC" OR agent_label = "OSX Notify"').fetchone() diff --git a/plexpy/users.py b/plexpy/users.py index 5c32d58c..b028bcd8 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -849,11 +849,15 @@ class Users(object): return filters_list - def set_user_login(self, user_id=None, user=None, user_group=None, ip_address=None, host=None, user_agent=None, success=0): + def set_user_login(self, user_id=None, user=None, user_group=None, ip_address=None, host=None, + user_agent=None, success=0, expiry=None, jwt_token=None): if user_id is None or str(user_id).isdigit(): monitor_db = database.MonitorDatabase() + if expiry is not None: + expiry = helpers.datetime_to_iso(expiry) + keys = {'timestamp': helpers.timestamp(), 'user_id': user_id} @@ -862,13 +866,29 @@ class Users(object): 'ip_address': ip_address, 'host': host, 'user_agent': user_agent, - 'success': success} + 'success': success, + 'expiry': expiry, + 'jwt_token': jwt_token} try: monitor_db.upsert(table_name='user_login', key_dict=keys, value_dict=values) except Exception as e: logger.warn("Tautulli Users :: Unable to execute database query for set_login_log: %s." % e) + def get_user_login(self, jwt_token): + monitor_db = database.MonitorDatabase() + result = monitor_db.select_single('SELECT * FROM user_login ' + 'WHERE jwt_token = ?', + [jwt_token]) + return result + + def clear_user_login_token(self, jwt_token): + monitor_db = database.MonitorDatabase() + result = monitor_db.select_single('UPDATE user_login SET jwt_token = NULL ' + 'WHERE jwt_token = ?', + [jwt_token]) + return result + def get_datatables_user_login(self, user_id=None, kwargs=None): default_return = {'recordsFiltered': 0, 'recordsTotal': 0, diff --git a/plexpy/webauth.py b/plexpy/webauth.py index 2272d7ab..052b8334 100644 --- a/plexpy/webauth.py +++ b/plexpy/webauth.py @@ -148,18 +148,28 @@ def check_credentials(username=None, password=None, token=None, admin_login='0', return False, None, None -def check_jwt_token(): +def get_jwt_token(): jwt_cookie = str(JWT_COOKIE_NAME + plexpy.CONFIG.PMS_UUID) jwt_token = cherrypy.request.cookie.get(jwt_cookie) + if jwt_token: + return jwt_token.value + + +def check_jwt_token(): + jwt_token = get_jwt_token() + if jwt_token: try: payload = jwt.decode( - jwt_token.value, plexpy.CONFIG.JWT_SECRET, leeway=timedelta(seconds=10), algorithms=[JWT_ALGORITHM] + jwt_token, plexpy.CONFIG.JWT_SECRET, leeway=timedelta(seconds=10), algorithms=[JWT_ALGORITHM] ) except (jwt.DecodeError, jwt.ExpiredSignatureError): return None + if not Users().get_user_login(jwt_token=jwt_token): + return None + return payload @@ -275,7 +285,8 @@ class AuthController(object): return raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT) - def on_login(self, username=None, user_id=None, user_group=None, success=False, oauth=False): + def on_login(self, username=None, user_id=None, user_group=None, success=False, oauth=False, + expiry=None, jwt_token=None): """Called on successful login""" # Save login to the database @@ -289,15 +300,21 @@ class AuthController(object): ip_address=ip_address, host=host, user_agent=user_agent, - success=success) + success=success, + expiry=expiry, + jwt_token=jwt_token) if success: use_oauth = 'Plex OAuth' if oauth else 'form' logger.debug("Tautulli WebAuth :: %s user '%s' logged into Tautulli using %s login." % (user_group.capitalize(), username, use_oauth)) - def on_logout(self, username, user_group): + def on_logout(self, username, user_group, jwt_token=None): """Called on logout""" + jwt_token = get_jwt_token() + if jwt_token: + Users().clear_user_login_token(jwt_token=jwt_token) + logger.debug("Tautulli WebAuth :: %s user '%s' logged out of Tautulli." % (user_group.capitalize(), username)) def get_loginform(self, redirect_uri=''): @@ -320,7 +337,8 @@ class AuthController(object): payload = check_jwt_token() if payload: - self.on_logout(payload['user'], payload['user_group']) + self.on_logout(username=payload['user'], + user_group=payload['user_group']) jwt_cookie = str(JWT_COOKIE_NAME + plexpy.CONFIG.PMS_UUID) cherrypy.response.cookie[jwt_cookie] = '' @@ -380,7 +398,9 @@ class AuthController(object): user_id=user_details['user_id'], user_group=user_group, success=True, - oauth=bool(token)) + oauth=bool(token), + expiry=expiry, + jwt_token=jwt_token) jwt_cookie = str(JWT_COOKIE_NAME + plexpy.CONFIG.PMS_UUID) cherrypy.response.cookie[jwt_cookie] = jwt_token