jellyfin-kodi/resources/lib/UserClient.py

455 lines
15 KiB
Python

#################################################################################################
# UserClient thread
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
import threading
import hashlib
import json as json
import KodiMonitor
import Utils as utils
from ClientInformation import ClientInformation
from DownloadUtils import DownloadUtils
from Player import Player
from API import API
class UserClient(threading.Thread):
# Borg - multiple instances, shared state
_shared_state = {}
clientInfo = ClientInformation()
doUtils = DownloadUtils()
KodiMonitor = KodiMonitor.Kodi_Monitor()
addonName = clientInfo.getAddonName()
addon = xbmcaddon.Addon()
WINDOW = xbmcgui.Window(10000)
stopClient = False
logLevel = int(addon.getSetting('logLevel'))
auth = True
retry = 0
currUser = None
currUserId = None
currServer = None
currToken = None
HasAccess = True
AdditionalUser = []
def __init__(self, *args):
self.__dict__ = self._shared_state
threading.Thread.__init__(self, *args)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), str(msg), int(lvl))
def getUsername(self):
username = utils.settings('username')
if (username == ""):
self.logMsg("No username saved.", 2)
return ""
return username
def getAdditionalUsers(self):
additionalUsers = utils.settings('additionalUsers')
if additionalUsers:
self.AdditionalUser = additionalUsers.split(',')
def getLogLevel(self):
try:
logLevel = int(utils.settings('logLevel'))
except:
logLevel = 0
return logLevel
def getUserId(self):
username = self.getUsername()
w_userId = self.WINDOW.getProperty('userId%s' % username)
s_userId = utils.settings('userId%s' % username)
# Verify the window property
if (w_userId != ""):
self.logMsg("Returning userId from WINDOW for username: %s UserId: %s" % (username, w_userId), 2)
return w_userId
# Verify the settings
elif (s_userId != ""):
self.logMsg("Returning userId from SETTINGS for username: %s userId: %s" % (username, s_userId), 2)
return s_userId
# No userId found
else:
self.logMsg("No userId saved for username: %s." % username)
return
def getServer(self, prefix=True):
alternate = utils.settings('altip') == "true"
# For https support
HTTPS = utils.settings('https')
host = utils.settings('ipaddress')
port = utils.settings('port')
# Alternate host
if alternate:
HTTPS = utils.settings('secondhttps')
host = utils.settings('secondipaddress')
port = utils.settings('secondport')
server = host + ":" + port
if host == "":
self.logMsg("No server information saved.", 2)
return ""
# If https is true
if prefix and (HTTPS == "true"):
server = "https://%s" % server
return server
# If https is false
elif prefix and (HTTPS == "false"):
server = "http://%s" % server
return server
# If only the host:port is required
elif (prefix == False):
return server
def getToken(self):
username = self.getUsername()
w_token = self.WINDOW.getProperty('accessToken%s' % username)
s_token = utils.settings('accessToken')
# Verify the window property
if (w_token != ""):
self.logMsg("Returning accessToken from WINDOW for username: %s accessToken: %s" % (username, w_token), 2)
return w_token
# Verify the settings
elif (s_token != ""):
self.logMsg("Returning accessToken from SETTINGS for username: %s accessToken: %s" % (username, s_token), 2)
self.WINDOW.setProperty('accessToken%s' % username, s_token)
return s_token
else:
self.logMsg("No token found.")
return ""
def getSSLverify(self):
# Verify host certificate
s_sslverify = utils.settings('sslverify')
if utils.settings('altip') == "true":
s_sslverify = utils.settings('secondsslverify')
if s_sslverify == "true":
return True
else:
return False
def getSSL(self):
# Client side certificate
s_cert = utils.settings('sslcert')
if utils.settings('altip') == "true":
s_cert = utils.settings('secondsslcert')
if s_cert == "None":
return None
else:
return s_cert
def setUserPref(self):
player = Player()
server = self.getServer()
userId = self.getUserId()
url = "{server}/mediabrowser/Users/{UserId}?format=json"
result = self.doUtils.downloadUrl(url)
# Set user image for skin display
self.WINDOW.setProperty("EmbyUserImage",API().getUserArtwork(result,"Primary"))
return True
def getPublicUsers(self):
server = self.getServer()
# Get public Users
url = "%s/mediabrowser/Users/Public?format=json" % server
result = self.doUtils.downloadUrl(url, authenticate=False)
users = []
if (result != ""):
users = result
else:
# Server connection failed
return False
return users
def hasAccess(self):
url = "{server}/mediabrowser/Users"
result = self.doUtils.downloadUrl(url)
if result is False:
# Access is restricted
self.logMsg("Access is restricted.")
self.HasAccess = False
return
elif self.WINDOW.getProperty('Server_online') != "true":
# Server connection failed
return
if self.WINDOW.getProperty("Server_status") == "restricted":
self.logMsg("Access is granted.")
self.HasAccess = True
self.WINDOW.setProperty("Server_status", "")
xbmcgui.Dialog().notification("Emby server", "Access is enabled.")
return
def loadCurrUser(self, authenticated=False):
WINDOW = self.WINDOW
doUtils = self.doUtils
username = self.getUsername()
# Only to be used if token exists
self.currUserId = self.getUserId()
self.currServer = self.getServer()
self.currToken = self.getToken()
self.ssl = self.getSSLverify()
self.sslcert = self.getSSL()
# Test the validity of current token
if authenticated == False:
url = "%s/mediabrowser/Users/%s" % (self.currServer, self.currUserId)
WINDOW.setProperty("currUser", username)
WINDOW.setProperty("accessToken%s" % username, self.currToken)
result = doUtils.downloadUrl(url)
if result == 401:
# Token is no longer valid
self.resetClient()
return False
# Set to windows property
WINDOW.setProperty("currUser", username)
WINDOW.setProperty("accessToken%s" % username, self.currToken)
WINDOW.setProperty("server%s" % username, self.currServer)
WINDOW.setProperty("server_%s" % username, self.getServer(prefix=False))
WINDOW.setProperty("userId%s" % username, self.currUserId)
# Set DownloadUtils values
doUtils.setUsername(username)
doUtils.setUserId(self.currUserId)
doUtils.setServer(self.currServer)
doUtils.setToken(self.currToken)
doUtils.setSSL(self.ssl, self.sslcert)
# parental control - let's verify if access is restricted
self.hasAccess()
# Start DownloadUtils session
doUtils.startSession()
self.getAdditionalUsers()
# Set user preferences in settings
self.setUserPref()
self.currUser = username
def authenticate(self):
WINDOW = self.WINDOW
addon = self.addon
username = self.getUsername()
server = self.getServer()
addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8')
hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
# If there's no settings.xml
if (hasSettings == 0):
self.logMsg("No settings.xml found.")
self.auth = False
return
# If no user information
if (server == "") or (username == ""):
self.logMsg("Missing server information.")
self.auth = False
return
# If there's a token
if (self.getToken() != ""):
result = self.loadCurrUser()
if result == False:
pass
else:
self.logMsg("Current user: %s" % self.currUser, 0)
self.logMsg("Current userId: %s" % self.currUserId, 0)
self.logMsg("Current accessToken: %s" % self.currToken, 0)
return
users = self.getPublicUsers()
password = ""
# Find user in list
for user in users:
name = user[u'Name']
userHasPassword = False
if (unicode(username, 'utf-8') in name):
# Verify if user has a password
if (user.get("HasPassword") == True):
userHasPassword = True
# If user has password
if (userHasPassword):
password = xbmcgui.Dialog().input("Enter password for user: %s" % username, option=xbmcgui.ALPHANUM_HIDE_INPUT)
# If password dialog is cancelled
if (password == ""):
self.logMsg("No password entered.", 0)
self.WINDOW.setProperty("Server_status", "Stop")
self.auth = False
return
break
else:
# Manual login, user is hidden
password = xbmcgui.Dialog().input("Enter password for user: %s" % username, option=xbmcgui.ALPHANUM_HIDE_INPUT)
sha1 = hashlib.sha1(password)
sha1 = sha1.hexdigest()
# Authenticate username and password
url = "%s/mediabrowser/Users/AuthenticateByName?format=json" % server
data = {'username': username, 'password': sha1}
self.logMsg(data, 2)
result = self.doUtils.downloadUrl(url, postBody=data, type="POST", authenticate=False)
accessToken = None
try:
self.logMsg("Auth_Reponse: %s" % result, 1)
accessToken = result[u'AccessToken']
except:
pass
if (result != None and accessToken != None):
self.currUser = username
xbmcgui.Dialog().notification("Emby server", "Welcome %s!" % self.currUser)
userId = result[u'User'][u'Id']
utils.settings("accessToken", accessToken)
utils.settings("userId%s" % username, userId)
self.logMsg("User Authenticated: %s" % accessToken)
self.loadCurrUser(authenticated=True)
self.WINDOW.setProperty("Server_status", "")
self.retry = 0
return
else:
self.logMsg("User authentication failed.")
utils.settings("accessToken", "")
utils.settings("userId%s" % username, "")
xbmcgui.Dialog().ok("Error connecting", "Invalid username or password.")
# Give two attempts at entering password
self.retry += 1
if self.retry == 2:
self.logMsg("Too many retries. You can retry by selecting the option in the addon settings.")
self.WINDOW.setProperty("Server_status", "Stop")
xbmcgui.Dialog().ok("Error connecting", "Failed to authenticate too many times. You can retry by selecting the option in the addon settings.")
self.auth = False
return
def resetClient(self):
username = self.getUsername()
self.logMsg("Reset UserClient authentication.", 1)
if (self.currToken != None):
# In case of 401, removed saved token
utils.settings("accessToken", "")
self.WINDOW.setProperty("accessToken%s" % username, "")
self.currToken = None
self.logMsg("User token has been removed.", 1)
self.auth = True
self.currUser = None
return
def run(self):
self.logMsg("|---- Starting UserClient ----|", 0)
while not self.KodiMonitor.abortRequested():
# Verify the log level
currLogLevel = self.getLogLevel()
if self.logLevel != currLogLevel:
# Set new log level
self.logLevel = currLogLevel
self.logMsg("New Log Level: %s" % currLogLevel, 0)
self.WINDOW.setProperty('getLogLevel', str(currLogLevel))
if (self.WINDOW.getProperty("Server_status") != ""):
status = self.WINDOW.getProperty("Server_status")
if status == "restricted":
# Parental control is restricting access
self.HasAccess = False
elif status == "401":
self.WINDOW.setProperty("Server_status", "Auth")
# Revoked token
self.resetClient()
if self.auth and (self.currUser == None):
status = self.WINDOW.getProperty("Server_status")
if (status == "") or (status == "Auth"):
self.auth = False
self.authenticate()
if (self.auth == False) and (self.currUser == None):
# Only if there's information found to login
server = self.getServer()
username = self.getUsername()
status = self.WINDOW.getProperty("Server_status")
# If user didn't enter a password when prompted
if status == "Stop":
pass
elif (server != "") and (username != ""):
self.logMsg("Server found: %s" % server)
self.logMsg("Username found: %s" % username)
self.auth = True
# If stopping the client didn't work
if self.stopClient == True:
break
if self.KodiMonitor.waitForAbort(1):
# Abort was requested while waiting. We should exit
break
self.doUtils.stopSession()
self.logMsg("|---- UserClient Stopped ----|", 0)
def stopClient(self):
# As last resort
self.stopClient = True