From 896a37bea9b03b24bf34d838ec6cf23ea699495f Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Thu, 11 Jan 2018 22:58:30 -0800 Subject: [PATCH] Allow Plex admin to login as Tautulli admin --- data/interfaces/default/settings.html | 31 ++++++++++++++++- plexpy/__init__.py | 11 +++++- plexpy/config.py | 1 + plexpy/plextv.py | 50 ++++++++++++++------------- plexpy/users.py | 9 +++-- plexpy/webauth.py | 44 +++++++++++++---------- plexpy/webserve.py | 13 ++++--- plexpy/webstart.py | 8 +++-- 8 files changed, 113 insertions(+), 54 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 9fc26905..0a2d2b9d 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -472,6 +472,13 @@ +
+ + +

Allow the Plex server admin to login as a Tautulli admin using their Plex.tv account.

+
+

Guest Access

@@ -1777,6 +1785,7 @@ $(document).ready(function() { $( ".auth-settings" ).change(function() { authChanged = true; + $("#auth_changed").prop('checked', true); }); $( ".directory-settings" ).change(function() { @@ -2016,6 +2025,26 @@ $(document).ready(function() { } }); + function allowPlexAdminCheck () { + if ($("#http_basic_auth").is(":checked")) { + $("#http_plex_admin").attr("disabled", true); + $("#http_plex_admin").attr("checked", false); + $("#allowPlexCheck").html("Plex admin login cannot be enabled with basic authentication."); + } else if ($('#http_username').val() == '' || $('#http_password').val() == '') { + $("#http_plex_admin").attr("disabled", true); + $("#http_plex_admin").attr("checked", false); + $("#allowPlexCheck").html("You must set an admin username and password above to allow Plex admin login."); + } else { + $("#http_plex_admin").attr("disabled", false); + $("#allowPlexCheck").html(""); + } + } + allowPlexAdminCheck(); + + $('#http_username, #http_password, #http_basic_auth').change(function () { + allowPlexAdminCheck(); + }); + function allowGuestAccessCheck () { if ($("#http_basic_auth").is(":checked")) { $("#allow_guest_access").attr("disabled", true); @@ -2024,7 +2053,7 @@ $(document).ready(function() { } else if ($('#http_username').val() == '' || $('#http_password').val() == '') { $("#allow_guest_access").attr("disabled", true); $("#allow_guest_access").attr("checked", false); - $("#allowGuestCheck").html("You must set an admin password above to allow guest access."); + $("#allowGuestCheck").html("You must set an admin username and password above to allow guest access."); } else { $("#allow_guest_access").attr("disabled", false); $("#allowGuestCheck").html(""); diff --git a/plexpy/__init__.py b/plexpy/__init__.py index b093148c..31e1a7f7 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -502,7 +502,7 @@ def dbcheck(): c_db.execute( 'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, ' 'user_id INTEGER DEFAULT NULL UNIQUE, username TEXT NOT NULL, friendly_name TEXT, ' - 'thumb TEXT, custom_avatar_url TEXT, email TEXT, is_home_user INTEGER DEFAULT NULL, ' + 'thumb TEXT, custom_avatar_url TEXT, email TEXT, is_admin INTEGER DEFAULT 0, is_home_user INTEGER DEFAULT NULL, ' 'is_allow_sync INTEGER DEFAULT NULL, is_restricted INTEGER DEFAULT NULL, do_notify INTEGER DEFAULT 1, ' 'keep_history INTEGER DEFAULT 1, deleted_user INTEGER DEFAULT 0, allow_guest INTEGER DEFAULT 0, ' 'user_token TEXT, server_token TEXT, shared_libraries TEXT, filter_all TEXT, filter_movies TEXT, filter_tv TEXT, ' @@ -1289,6 +1289,15 @@ def dbcheck(): 'ALTER TABLE users ADD COLUMN filter_photos TEXT' ) + # Upgrade users table from earlier versions + try: + c_db.execute('SELECT is_admin FROM users') + except sqlite3.OperationalError: + logger.debug(u"Altering database. Updating database table users.") + c_db.execute( + 'ALTER TABLE users ADD COLUMN is_admin INTEGER DEFAULT 0' + ) + # Upgrade notify_log table from earlier versions try: c_db.execute('SELECT poster_url FROM notify_log') diff --git a/plexpy/config.py b/plexpy/config.py index 7bd326a7..9d7b72b2 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -225,6 +225,7 @@ _CONFIG_DEFINITIONS = { 'HTTP_PROXY': (int, 'General', 0), 'HTTP_ROOT': (str, 'General', ''), 'HTTP_USERNAME': (str, 'General', ''), + 'HTTP_PLEX_ADMIN': (int, 'General', 0), 'HIPCHAT_URL': (str, 'Hipchat', ''), 'HIPCHAT_COLOR': (str, 'Hipchat', ''), 'HIPCHAT_INCL_SUBJECT': (int, 'Hipchat', 1), diff --git a/plexpy/plextv.py b/plexpy/plextv.py index ed6597cb..7cf7ff74 100644 --- a/plexpy/plextv.py +++ b/plexpy/plextv.py @@ -331,18 +331,19 @@ class PlexTV(object): for a in xml_head: own_details = {"user_id": helpers.get_xml_attr(a, 'id'), - "username": helpers.get_xml_attr(a, 'username'), - "thumb": helpers.get_xml_attr(a, 'thumb'), - "email": helpers.get_xml_attr(a, 'email'), - "is_home_user": helpers.get_xml_attr(a, 'home'), - "is_allow_sync": None, - "is_restricted": helpers.get_xml_attr(a, 'restricted'), - "filter_all": helpers.get_xml_attr(a, 'filterAll'), - "filter_movies": helpers.get_xml_attr(a, 'filterMovies'), - "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'), - "filter_music": helpers.get_xml_attr(a, 'filterMusic'), - "filter_photos": helpers.get_xml_attr(a, 'filterPhotos') - } + "username": helpers.get_xml_attr(a, 'username'), + "thumb": helpers.get_xml_attr(a, 'thumb'), + "email": helpers.get_xml_attr(a, 'email'), + "is_home_user": helpers.get_xml_attr(a, 'home'), + "is_admin": 1, + "is_allow_sync": None, + "is_restricted": helpers.get_xml_attr(a, 'restricted'), + "filter_all": helpers.get_xml_attr(a, 'filterAll'), + "filter_movies": helpers.get_xml_attr(a, 'filterMovies'), + "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'), + "filter_music": helpers.get_xml_attr(a, 'filterMusic'), + "filter_photos": helpers.get_xml_attr(a, 'filterPhotos') + } users_list.append(own_details) @@ -354,18 +355,19 @@ class PlexTV(object): for a in xml_head: friend = {"user_id": helpers.get_xml_attr(a, 'id'), - "username": helpers.get_xml_attr(a, 'title'), - "thumb": helpers.get_xml_attr(a, 'thumb'), - "email": helpers.get_xml_attr(a, 'email'), - "is_home_user": helpers.get_xml_attr(a, 'home'), - "is_allow_sync": helpers.get_xml_attr(a, 'allowSync'), - "is_restricted": helpers.get_xml_attr(a, 'restricted'), - "filter_all": helpers.get_xml_attr(a, 'filterAll'), - "filter_movies": helpers.get_xml_attr(a, 'filterMovies'), - "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'), - "filter_music": helpers.get_xml_attr(a, 'filterMusic'), - "filter_photos": helpers.get_xml_attr(a, 'filterPhotos') - } + "username": helpers.get_xml_attr(a, 'title'), + "thumb": helpers.get_xml_attr(a, 'thumb'), + "email": helpers.get_xml_attr(a, 'email'), + "is_admin": 0, + "is_home_user": helpers.get_xml_attr(a, 'home'), + "is_allow_sync": helpers.get_xml_attr(a, 'allowSync'), + "is_restricted": helpers.get_xml_attr(a, 'restricted'), + "filter_all": helpers.get_xml_attr(a, 'filterAll'), + "filter_movies": helpers.get_xml_attr(a, 'filterMovies'), + "filter_tv": helpers.get_xml_attr(a, 'filterTelevision'), + "filter_music": helpers.get_xml_attr(a, 'filterMusic'), + "filter_photos": helpers.get_xml_attr(a, 'filterPhotos') + } users_list.append(friend) diff --git a/plexpy/users.py b/plexpy/users.py index 4ba3ee9d..6bbab6b7 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -52,6 +52,7 @@ def refresh_users(): new_value_dict = {"username": item['username'], "thumb": item['thumb'], "email": item['email'], + "is_admin": item['is_admin'], "is_home_user": item['is_home_user'], "is_allow_sync": item['is_allow_sync'], "is_restricted": item['is_restricted'], @@ -330,6 +331,7 @@ class Users(object): 'friendly_name': 'Local', 'user_thumb': common.DEFAULT_USER_THUMB, 'email': '', + 'is_admin': '', 'is_home_user': 0, 'is_allow_sync': 0, 'is_restricted': 0, @@ -349,21 +351,21 @@ class Users(object): try: if str(user_id).isdigit(): query = 'SELECT user_id, username, friendly_name, thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \ - 'email, is_home_user, is_allow_sync, is_restricted, do_notify, keep_history, deleted_user, ' \ + 'email, is_admin, is_home_user, is_allow_sync, is_restricted, do_notify, keep_history, deleted_user, ' \ 'allow_guest, shared_libraries ' \ 'FROM users ' \ 'WHERE user_id = ? ' result = monitor_db.select(query, args=[user_id]) elif user: query = 'SELECT user_id, username, friendly_name, thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \ - 'email, is_home_user, is_allow_sync, is_restricted, do_notify, keep_history, deleted_user, ' \ + 'email, is_admin, is_home_user, is_allow_sync, is_restricted, do_notify, keep_history, deleted_user, ' \ 'allow_guest, shared_libraries ' \ 'FROM users ' \ 'WHERE username = ? COLLATE NOCASE ' result = monitor_db.select(query, args=[user]) elif email: query = 'SELECT user_id, username, friendly_name, thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \ - 'email, is_home_user, is_allow_sync, is_restricted, do_notify, keep_history, deleted_user, ' \ + 'email, is_admin, is_home_user, is_allow_sync, is_restricted, do_notify, keep_history, deleted_user, ' \ 'allow_guest, shared_libraries ' \ 'FROM users ' \ 'WHERE email = ? COLLATE NOCASE ' @@ -398,6 +400,7 @@ class Users(object): 'friendly_name': friendly_name, 'user_thumb': user_thumb, 'email': item['email'], + 'is_admin': item['is_admin'], 'is_home_user': item['is_home_user'], 'is_allow_sync': item['is_allow_sync'], 'is_restricted': item['is_restricted'], diff --git a/plexpy/webauth.py b/plexpy/webauth.py index 8819c935..928ec77e 100644 --- a/plexpy/webauth.py +++ b/plexpy/webauth.py @@ -54,10 +54,17 @@ def user_login(username=None, password=None): if user_id != str(user_details['user_id']): # The user is not in the database. return None + elif plexpy.CONFIG.HTTP_PLEX_ADMIN and user_details['is_admin']: + # Plex admin login + return 'admin' elif not user_details['allow_guest'] or user_details['deleted_user']: # Guest access is disabled or the user is deleted. return None + # Stop here if guest access is not enabled + if not plexpy.CONFIG.ALLOW_GUEST_ACCESS: + return None + # The user is in the database, and guest access is enabled, so try to retrieve a server token. # If a server token is returned, then the user is a valid friend of the server. plex_tv = PlexTV(token=user_token) @@ -75,7 +82,7 @@ def user_login(username=None, password=None): # Refresh the users list to make sure we have all the correct permissions. refresh_users() # Successful login - return True + return 'guest' else: logger.warn(u"Tautulli WebAuth :: Unable to register user '%s' in database." % username) return None @@ -96,16 +103,20 @@ def check_credentials(username, password, admin_login='0'): """Verifies credentials for username and password. Returns True and the user group on success or False and no user group""" - if plexpy.CONFIG.HTTP_HASHED_PASSWORD and \ - username == plexpy.CONFIG.HTTP_USERNAME and check_hash(password, plexpy.CONFIG.HTTP_PASSWORD): - return True, u'admin' - elif not plexpy.CONFIG.HTTP_HASHED_PASSWORD and \ - username == plexpy.CONFIG.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD: - return True, u'admin' - elif not admin_login == '1' and plexpy.CONFIG.ALLOW_GUEST_ACCESS and user_login(username, password): - return True, u'guest' - else: - return False, None + if plexpy.CONFIG.HTTP_PASSWORD: + if plexpy.CONFIG.HTTP_HASHED_PASSWORD and \ + username == plexpy.CONFIG.HTTP_USERNAME and check_hash(password, plexpy.CONFIG.HTTP_PASSWORD): + return True, 'admin' + elif not plexpy.CONFIG.HTTP_HASHED_PASSWORD and \ + username == plexpy.CONFIG.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD: + return True, 'admin' + + if plexpy.CONFIG.HTTP_PLEX_ADMIN or (not admin_login == '1' and plexpy.CONFIG.ALLOW_GUEST_ACCESS): + plex_login = user_login(username, password) + if plex_login is not None: + return True, plex_login + + return False, None def check_jwt_token(): @@ -163,15 +174,12 @@ def requireAuth(*conditions): # # Define those at will however suits the application. -def member_of(groupname): - def check(): - # replace with actual check if is in - return cherrypy.request.login['user'] == plexpy.CONFIG.HTTP_USERNAME and groupname == 'admin' - return check +def member_of(user_group): + return cherrypy.request.login['user_group'] == user_group -def name_is(reqd_username): - return lambda: reqd_username == cherrypy.request.login['user'] +def name_is(user_name): + return cherrypy.request.login['user'] == user_name # These might be handy diff --git a/plexpy/webserve.py b/plexpy/webserve.py index fe5e65e0..f58bbd61 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -2538,6 +2538,7 @@ class WebInterface(object): "http_password": http_password, "http_root": plexpy.CONFIG.HTTP_ROOT, "http_proxy": checked(plexpy.CONFIG.HTTP_PROXY), + "http_plex_admin": checked(plexpy.CONFIG.HTTP_PLEX_ADMIN), "launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER), "enable_https": checked(plexpy.CONFIG.ENABLE_HTTPS), "https_create_cert": checked(plexpy.CONFIG.HTTPS_CREATE_CERT), @@ -2632,7 +2633,7 @@ class WebInterface(object): "monitor_pms_updates", "monitor_remote_access", "get_file_sizes", "log_blacklist", "http_hash_password", "allow_guest_access", "cache_images", "http_proxy", "http_basic_auth", "notify_concurrent_by_ip", "history_table_activity", "plexpy_auto_update", - "themoviedb_lookup", "tvmaze_lookup" + "themoviedb_lookup", "tvmaze_lookup", "http_plex_admin" ] for checked_config in checked_configs: if checked_config not in kwargs: @@ -2673,8 +2674,7 @@ class WebInterface(object): refresh_users = False # First run from the setup wizard - if kwargs.get('first_run'): - del kwargs['first_run'] + if kwargs.pop('first_run', None): first_run = True # If we change any monitoring settings, make sure we reschedule tasks. @@ -2728,12 +2728,15 @@ class WebInterface(object): refresh_libraries = True # If we change the server, make sure we grab the new url and refresh libraries and users lists. - if kwargs.get('server_changed'): - del kwargs['server_changed'] + if kwargs.pop('server_changed', None): server_changed = True refresh_users = True refresh_libraries = True + # If we change the authentication settings, make sure we refresh the users lists. + if kwargs.pop('auth_changed', None): + refresh_users = True + plexpy.CONFIG.process_kwargs(kwargs) # Write the config diff --git a/plexpy/webstart.py b/plexpy/webstart.py index 87276b7e..63f5ddeb 100644 --- a/plexpy/webstart.py +++ b/plexpy/webstart.py @@ -68,8 +68,12 @@ def initialize(options): protocol = "http" if options['http_password']: - logger.info(u"Tautulli WebStart :: Web server authentication is enabled, username is '%s'", - options['http_username']) + login_allowed = ["Tautulli admin (username is '%s')" % options['http_username']] + if plexpy.CONFIG.HTTP_PLEX_ADMIN: + login_allowed.append("Plex admin") + + logger.info(u"Tautulli WebStart :: Web server authentication is enabled: %s allowed", ' and '.join(login_allowed)) + if options['http_basic_auth']: auth_enabled = False basic_auth_enabled = True