diff --git a/plexpy/users.py b/plexpy/users.py index b028bcd8..d3e5103e 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -21,6 +21,7 @@ from future.builtins import str from future.builtins import object from future.moves.urllib.parse import parse_qsl +import arrow import httpagentparser from datetime import datetime, timedelta @@ -882,14 +883,33 @@ class Users(object): [jwt_token]) return result - def clear_user_login_token(self, jwt_token): + def clear_user_login_token(self, jwt_token=None, row_ids=None): 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): + if jwt_token: + logger.debug("Tautulli Users :: Clearing user JWT token.") + try: + monitor_db.action('UPDATE user_login SET jwt_token = NULL ' + 'WHERE jwt_token = ?', + [jwt_token]) + except Exception as e: + logger.error("Tautulli Users :: Unable to clear user JWT token: %s.", e) + return False + + elif row_ids and row_ids is not None: + row_ids = list(map(helpers.cast_to_int, row_ids.split(','))) + logger.debug("Tautulli Users :: Clearing JWT tokens for row_ids %s.", row_ids) + try: + monitor_db.action('UPDATE user_login SET jwt_token = NULL ' + 'WHERE id in ({})'.format(','.join(['?'] * len(row_ids))), + row_ids) + except Exception as e: + logger.error("Tautulli Users :: Unable to clear JWT tokens: %s.", e) + return False + + return True + + def get_datatables_user_login(self, user_id=None, jwt_token=None, kwargs=None): default_return = {'recordsFiltered': 0, 'recordsTotal': 0, 'draw': 0, @@ -905,7 +925,8 @@ class Users(object): else: custom_where = [['user_login.user_id', user_id]] if user_id else [] - columns = ['user_login.timestamp', + columns = ['user_login.id AS row_id', + 'user_login.timestamp', 'user_login.user_id', 'user_login.user', 'user_login.user_group', @@ -913,6 +934,8 @@ class Users(object): 'user_login.host', 'user_login.user_agent', 'user_login.success', + 'user_login.expiry', + 'user_login.jwt_token', '(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \ THEN users.username ELSE users.friendly_name END) AS friendly_name' ] @@ -936,7 +959,16 @@ class Users(object): for item in results: (os, browser) = httpagentparser.simple_detect(item['user_agent']) - row = {'timestamp': item['timestamp'], + expiry = None + current = False + if item['jwt_token'] and item['expiry']: + _expiry = helpers.iso_to_datetime(item['expiry']) + if _expiry > arrow.now(): + expiry = _expiry.strftime('%Y-%m-%d %H:%M:%S') + current = (item['jwt_token'] == jwt_token) + + row = {'row_id': item['row_id'], + 'timestamp': item['timestamp'], 'user_id': item['user_id'], 'user_group': item['user_group'], 'ip_address': item['ip_address'], @@ -945,6 +977,8 @@ class Users(object): 'os': os, 'browser': browser, 'success': item['success'], + 'expiry': expiry, + 'current': current, 'friendly_name': item['friendly_name'] or item['user'] } diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 4130fd87..dba05ee8 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -73,7 +73,7 @@ if plexpy.PYTHON2: from api2 import API2 from helpers import checked, addtoapi, get_ip, create_https_certificates, build_datatables_json, sanitize_out from session import get_session_info, get_session_user_id, allow_session_user, allow_session_library - from webauth import AuthController, requireAuth, member_of, check_auth + from webauth import AuthController, requireAuth, member_of, check_auth, get_jwt_token if common.PLATFORM == 'Windows': import windows elif common.PLATFORM == 'Darwin': @@ -107,7 +107,7 @@ else: from plexpy.api2 import API2 from plexpy.helpers import checked, addtoapi, get_ip, create_https_certificates, build_datatables_json, sanitize_out from plexpy.session import get_session_info, get_session_user_id, allow_session_user, allow_session_library - from plexpy.webauth import AuthController, requireAuth, member_of, check_auth + from plexpy.webauth import AuthController, requireAuth, member_of, check_auth, get_jwt_token if common.PLATFORM == 'Windows': from plexpy import windows elif common.PLATFORM == 'Darwin': @@ -1556,10 +1556,13 @@ class WebInterface(object): "recordsFiltered": 10, "data": [{"browser": "Safari 7.0.3", + "current": false, + "expiry": "2021-06-30 18:48:03", "friendly_name": "Jon Snow", "host": "http://plexpy.castleblack.com", "ip_address": "xxx.xxx.xxx.xxx", "os": "Mac OS X", + "row_id": 1, "timestamp": 1462591869, "user": "LordCommanderSnow", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A", @@ -1583,11 +1586,41 @@ class WebInterface(object): ("browser", True, True)] kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "timestamp") + jwt_token = get_jwt_token() + user_data = users.Users() - history = user_data.get_datatables_user_login(user_id=user_id, kwargs=kwargs) + history = user_data.get_datatables_user_login(user_id=user_id, + jwt_token=jwt_token, + kwargs=kwargs) return history + @cherrypy.expose + @cherrypy.tools.json_out() + @requireAuth(member_of("admin")) + @addtoapi() + def logout_user_session(self, row_ids=None, **kwargs): + """ Logout Tautulli user sessions. + + ``` + Required parameters: + row_ids (str): Comma separated row ids to sign out, e.g. "2,3,8" + + Optional parameters: + None + + Returns: + None + ``` + """ + user_data = users.Users() + result = user_data.clear_user_login_token(row_ids=row_ids) + + if result: + return {'result': 'success', 'message': 'Users session logged out.'} + else: + return {'result': 'error', 'message': 'Unable to logout user session.'} + @cherrypy.expose @cherrypy.tools.json_out() @requireAuth(member_of("admin"))