mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2024-12-25 18:26:15 +00:00
354f0905f6
I had to reorganize the service.py loop a bit for this to work. Logically the top level inside the while loop should be if the user is authenticated.
388 lines
No EOL
13 KiB
Python
388 lines
No EOL
13 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
|
|
|
|
|
|
class UserClient(threading.Thread):
|
|
|
|
# Borg - multiple instances, shared state
|
|
_shared_state = {}
|
|
|
|
clientInfo = ClientInformation()
|
|
doUtils = DownloadUtils()
|
|
KodiMonitor = KodiMonitor.Kodi_Monitor()
|
|
|
|
addonName = clientInfo.getAddonName()
|
|
addonId = clientInfo.getAddonId()
|
|
addon = xbmcaddon.Addon(id=addonId)
|
|
WINDOW = xbmcgui.Window(10000)
|
|
|
|
stopClient = False
|
|
logLevel = 0
|
|
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):
|
|
|
|
addon = xbmcaddon.Addon(id=self.addonId)
|
|
username = addon.getSetting('username')
|
|
|
|
if (username == ""):
|
|
self.logMsg("No username saved.", 2)
|
|
return ""
|
|
|
|
return username
|
|
|
|
def getLogLevel(self):
|
|
|
|
logLevel = int(self.addon.getSetting('logLevel'))
|
|
return logLevel
|
|
|
|
def getUserId(self):
|
|
|
|
username = self.getUsername()
|
|
w_userId = self.WINDOW.getProperty('userId%s' % username)
|
|
s_userId = self.addon.getSetting('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):
|
|
|
|
# For https support
|
|
addon = xbmcaddon.Addon(id=self.addonId)
|
|
HTTPS = addon.getSetting('https')
|
|
host = addon.getSetting('ipaddress')
|
|
port = addon.getSetting('port')
|
|
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 = self.addon.getSetting('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 = self.addon.getSetting('sslverify')
|
|
|
|
if s_sslverify == "true":
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def getSSL(self):
|
|
# Client side certificate
|
|
s_cert = self.addon.getSetting('sslcert')
|
|
|
|
if s_cert == "None":
|
|
return None
|
|
else:
|
|
return s_cert
|
|
|
|
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
|
|
|
|
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):
|
|
|
|
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()
|
|
|
|
# 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)
|
|
# Start DownloadUtils session
|
|
doUtils.startSession()
|
|
|
|
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'))
|
|
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() != ""):
|
|
self.loadCurrUser()
|
|
self.logMsg("Current user: %s" % self.currUser, 0)
|
|
self.logMsg("Current userId: %s" % self.currUserId, 0)
|
|
self.logMsg("Current accessToken: %s" % self.currToken, 0)
|
|
# parental control - let's verify if access is restricted
|
|
self.hasAccess()
|
|
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
|
|
userId = result[u'User'][u'Id']
|
|
addon.setSetting("accessToken", accessToken)
|
|
addon.setSetting("userId%s" % username, userId)
|
|
self.logMsg("User Authenticated: %s" % accessToken)
|
|
self.loadCurrUser()
|
|
self.WINDOW.setProperty("Server_status", "")
|
|
self.retry = 0
|
|
return
|
|
else:
|
|
self.logMsg("User authentication failed.")
|
|
addon.setSetting("accessToken", "")
|
|
addon.setSetting("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):
|
|
|
|
if (self.currToken != None):
|
|
# In case of 401, removed saved token
|
|
self.addon.setSetting("accessToken%s" % self.currUser, "")
|
|
self.WINDOW.setProperty("accessToken%s" % self.currUser, "")
|
|
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():
|
|
|
|
# Get the latest addon settings
|
|
self.addon = xbmcaddon.Addon(id=self.addonId)
|
|
|
|
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.logMsg("|---- UserClient Stopped ----|", 0)
|
|
|
|
def stopClient(self):
|
|
# As last resort
|
|
self.stopClient = True |