+
+
+
+
+
+
+
+
@@ -78,25 +93,91 @@
diff --git a/plexpy/plextv.py b/plexpy/plextv.py
index 47704236..73604485 100644
--- a/plexpy/plextv.py
+++ b/plexpy/plextv.py
@@ -226,6 +226,45 @@ class PlexTV(object):
return server_token
+ def get_plextv_pin(self, pin='', output_format=''):
+ if pin:
+ uri = '/api/v2/pins/' + pin
+ request = self.request_handler.make_request(uri=uri,
+ request_type='GET',
+ output_format=output_format,
+ no_token=True)
+ else:
+ uri = '/api/v2/pins?strong=true'
+ request = self.request_handler.make_request(uri=uri,
+ request_type='POST',
+ output_format=output_format,
+ no_token=True)
+ return request
+
+ def get_pin(self, pin=''):
+ plextv_response = self.get_plextv_pin(pin=pin,
+ output_format='xml')
+
+ if plextv_response:
+ try:
+ xml_head = plextv_response.getElementsByTagName('pin')
+ if xml_head:
+ pin = {'id': xml_head[0].getAttribute('id'),
+ 'code': xml_head[0].getAttribute('code'),
+ 'token': xml_head[0].getAttribute('authToken')
+ }
+ return pin
+ else:
+ logger.warn(u"Tautulli PlexTV :: Could not get Plex authentication pin.")
+ return None
+
+ except Exception as e:
+ logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_pin: %s." % e)
+ return None
+
+ else:
+ return None
+
def get_plextv_user_data(self):
plextv_response = self.get_plex_auth(output_format='dict')
@@ -819,3 +858,28 @@ class PlexTV(object):
return True
else:
return False
+
+ def get_plex_account_details(self):
+ account_data = self.get_plextv_user_details(output_format='xml')
+
+ try:
+ xml_head = account_data.getElementsByTagName('user')
+ except Exception as e:
+ logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_plex_account_details: %s." % e)
+ return None
+
+ for a in xml_head:
+ account_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_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'),
+ "user_token": helpers.get_xml_attr(a, 'authToken')
+ }
+ return account_details
diff --git a/plexpy/webauth.py b/plexpy/webauth.py
index 856caa93..1fee7323 100644
--- a/plexpy/webauth.py
+++ b/plexpy/webauth.py
@@ -19,7 +19,6 @@
# Session tool to be loaded.
from datetime import datetime, timedelta
-import re
from urllib import quote, unquote
import cherrypy
@@ -37,17 +36,27 @@ JWT_ALGORITHM = 'HS256'
JWT_COOKIE_NAME = 'tautulli_token_'
-def user_login(username=None, password=None):
- if not username or not password:
- return None
+def user_login(username=None, password=None, token=None):
+ user_token = None
+ user_id = None
# Try to login to Plex.tv to check if the user has a vaild account
- plex_tv = PlexTV(username=username, password=password)
- plex_user = plex_tv.get_token()
- if plex_user:
- user_token = plex_user['auth_token']
- user_id = plex_user['user_id']
+ if username and password:
+ plex_tv = PlexTV(username=username, password=password)
+ plex_user = plex_tv.get_token()
+ if plex_user:
+ user_token = plex_user['auth_token']
+ user_id = plex_user['user_id']
+ elif token:
+ plex_tv = PlexTV(token=token)
+ plex_user = plex_tv.get_plex_account_details()
+ if plex_user:
+ user_token = token
+ user_id = plex_user['user_id']
+ else:
+ return None
+ if user_token and user_id:
# Try to retrieve the user from the database.
# Also make sure guest access is enabled for the user and the user is not deleted.
user_data = Users()
@@ -57,7 +66,7 @@ def user_login(username=None, password=None):
return None
elif plexpy.CONFIG.HTTP_PLEX_ADMIN and user_details['is_admin']:
# Plex admin login
- return 'admin'
+ return user_details, 'admin'
elif not user_details['allow_guest'] or user_details['deleted_user']:
# Guest access is disabled or the user is deleted.
return None
@@ -75,49 +84,64 @@ def user_login(username=None, password=None):
# Register the new user / update the access tokens.
monitor_db = MonitorDatabase()
try:
- logger.debug(u"Tautulli WebAuth :: Regestering tokens for user '%s' in the database." % username)
- result = monitor_db.action('UPDATE users SET user_token = ?, server_token = ? WHERE user_id = ?',
- [user_token, server_token, user_id])
+ logger.debug(u"Tautulli WebAuth :: Regestering tokens for user '%s' in the database."
+ % user_details['username'])
+ result = monitor_db.action('UPDATE users SET server_token = ? WHERE user_id = ?',
+ [server_token, user_details['user_id']])
if result:
# Refresh the users list to make sure we have all the correct permissions.
refresh_users()
# Successful login
- return 'guest'
+ return user_details, 'guest'
else:
- logger.warn(u"Tautulli WebAuth :: Unable to register user '%s' in database." % username)
+ logger.warn(u"Tautulli WebAuth :: Unable to register user '%s' in database."
+ % user_details['username'])
return None
except Exception as e:
- logger.warn(u"Tautulli WebAuth :: Unable to register user '%s' in database: %s." % (username, e))
+ logger.warn(u"Tautulli WebAuth :: Unable to register user '%s' in database: %s."
+ % (user_details['username'], e))
return None
else:
- logger.warn(u"Tautulli WebAuth :: Unable to retrieve Plex.tv server token for user '%s'." % username)
+ logger.warn(u"Tautulli WebAuth :: Unable to retrieve Plex.tv server token for user '%s'."
+ % user_details['username'])
return None
- else:
+ elif username:
logger.warn(u"Tautulli WebAuth :: Unable to retrieve Plex.tv user token for user '%s'." % username)
return None
- return None
+ elif token:
+ logger.warn(u"Tautulli WebAuth :: Unable to retrieve Plex.tv user token for Plex OAuth.")
+ return None
-def check_credentials(username, password, admin_login='0'):
+def check_credentials(username=None, password=None, token=None, 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_PASSWORD:
- if plexpy.CONFIG.HTTP_HASHED_PASSWORD and \
- username == plexpy.CONFIG.HTTP_USERNAME and check_hash(password, plexpy.CONFIG.HTTP_PASSWORD):
- return True, 'tautulli admin'
- elif not plexpy.CONFIG.HTTP_HASHED_PASSWORD and \
- username == plexpy.CONFIG.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD:
- return True, 'tautulli admin'
+ if username and password:
+ if plexpy.CONFIG.HTTP_PASSWORD:
+ user_details = {'user_id': None, 'username': username}
- 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
+ if plexpy.CONFIG.HTTP_HASHED_PASSWORD and \
+ username == plexpy.CONFIG.HTTP_USERNAME and check_hash(password, plexpy.CONFIG.HTTP_PASSWORD):
+ return True, user_details, 'admin'
+ elif not plexpy.CONFIG.HTTP_HASHED_PASSWORD and \
+ username == plexpy.CONFIG.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD:
+ return True, user_details, 'admin'
- return False, None
+ if plexpy.CONFIG.HTTP_PLEX_ADMIN or (not admin_login == '1' and plexpy.CONFIG.ALLOW_GUEST_ACCESS):
+ plex_login = user_login(username=username, password=password)
+ if plex_login is not None:
+ return True, plex_login[0], plex_login[1]
+
+ elif token:
+ if plexpy.CONFIG.HTTP_PLEX_ADMIN or (not admin_login == '1' and plexpy.CONFIG.ALLOW_GUEST_ACCESS):
+ plex_login = user_login(token=token)
+ if plex_login is not None:
+ return True, plex_login[0], plex_login[1]
+
+ return False, None, None
def check_jwt_token():
@@ -279,41 +303,33 @@ class AuthController(object):
@cherrypy.expose
@cherrypy.tools.json_out()
- def signin(self, username=None, password=None, remember_me='0', admin_login='0', *args, **kwargs):
+ def signin(self, username=None, password=None, token=None, remember_me='0', admin_login='0', *args, **kwargs):
if cherrypy.request.method != 'POST':
cherrypy.response.status = 405
return {'status': 'error', 'message': 'Sign in using POST.'}
- error_message = {'status': 'error', 'message': 'Incorrect username or password.'}
+ error_message = {'status': 'error', 'message': 'Invalid credentials.'}
- valid_login, user_group = check_credentials(username, password, admin_login)
+ valid_login, user_details, user_group = check_credentials(username=username,
+ password=password,
+ token=token,
+ admin_login=admin_login)
if valid_login:
- if user_group == 'tautulli admin':
- user_group = 'admin'
- user_id = None
- else:
- if re.match(r"[^@]+@[^@]+\.[^@]+", username):
- user_details = Users().get_details(email=username)
- else:
- user_details = Users().get_details(user=username)
-
- user_id = user_details['user_id']
-
time_delta = timedelta(days=30) if remember_me == '1' else timedelta(minutes=60)
expiry = datetime.utcnow() + time_delta
payload = {
- 'user_id': user_id,
- 'user': username,
+ 'user_id': user_details['user_id'],
+ 'user': user_details['username'],
'user_group': user_group,
'exp': expiry
}
jwt_token = jwt.encode(payload, plexpy.CONFIG.JWT_SECRET, algorithm=JWT_ALGORITHM)
- self.on_login(username=username,
- user_id=user_id,
+ self.on_login(username=user_details['username'],
+ user_id=user_details['user_id'],
user_group=user_group,
success=1)
@@ -326,14 +342,36 @@ class AuthController(object):
cherrypy.response.status = 200
return {'status': 'success', 'token': jwt_token.decode('utf-8'), 'uuid': plexpy.CONFIG.PMS_UUID}
- elif admin_login == '1':
+ elif admin_login == '1' and username:
self.on_login(username=username)
logger.debug(u"Tautulli WebAuth :: Invalid admin login attempt from '%s'." % username)
cherrypy.response.status = 401
return error_message
- else:
+ elif username:
self.on_login(username=username)
- logger.debug(u"Tautulli WebAuth :: Invalid login attempt from '%s'." % username)
+ logger.debug(u"Tautulli WebAuth :: Invalid user login attempt from '%s'." % username)
cherrypy.response.status = 401
return error_message
+
+ elif token:
+ logger.debug(u"Tautulli WebAuth :: Invalid Plex OAuth login attempt.")
+ cherrypy.response.status = 401
+ return error_message
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def get_plex_oauth_url(self, *args, **kwargs):
+ pin = PlexTV().get_pin()
+ oauth_url = 'https://app.plex.tv/auth/#!?clientID={}&code={}'.format(
+ plexpy.CONFIG.PMS_UUID, pin['code'])
+ return {'pin': pin['id'], 'url': oauth_url}
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def pin(self, pin=None, *args, **kwargs):
+ pin = PlexTV().get_pin(pin=pin)
+ if pin['token']:
+ return {'result': 'success', 'token': pin['token']}
+ else:
+ return {'result': 'polling'}