This commit is contained in:
SpootDev 2016-03-31 13:40:47 -05:00
parent 8a130fe810
commit 2748d21ff4

View file

@ -1,487 +1,480 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
################################################################################################## ##################################################################################################
import hashlib import hashlib
import threading import threading
import xbmc import xbmc
import xbmcgui import xbmcgui
import xbmcaddon import xbmcaddon
import xbmcvfs import xbmcvfs
import artwork import artwork
import utils import utils
import clientinfo import clientinfo
import downloadutils import downloadutils
################################################################################################## ##################################################################################################
class UserClient(threading.Thread): class UserClient(threading.Thread):
# Borg - multiple instances, shared state # Borg - multiple instances, shared state
_shared_state = {} _shared_state = {}
stopClient = False stopClient = False
auth = True auth = True
retry = 0 retry = 0
currUser = None currUser = None
currUserId = None currUserId = None
currServer = None currServer = None
currToken = None currToken = None
HasAccess = True HasAccess = True
AdditionalUser = [] AdditionalUser = []
userSettings = None userSettings = None
def __init__(self): def __init__(self):
self.__dict__ = self._shared_state self.__dict__ = self._shared_state
self.addon = xbmcaddon.Addon() self.addon = xbmcaddon.Addon()
self.addonName = clientinfo.ClientInfo().getAddonName() self.addonName = clientinfo.ClientInfo().getAddonName()
self.doUtils = downloadutils.DownloadUtils() self.doUtils = downloadutils.DownloadUtils()
threading.Thread.__init__(self) threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1): def logMsg(self, msg, lvl=1):
className = self.__class__.__name__ className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def getAdditionalUsers(self): def getAdditionalUsers(self):
additionalUsers = utils.settings('additionalUsers') additionalUsers = utils.settings('additionalUsers')
if additionalUsers: if additionalUsers:
self.AdditionalUser = additionalUsers.split(',') self.AdditionalUser = additionalUsers.split(',')
def getUsername(self): def getUsername(self):
username = utils.settings('username') username = utils.settings('username')
if not username: if not username:
self.logMsg("No username saved.", 2) self.logMsg("No username saved.", 2)
return "" return ""
return username return username
def getLogLevel(self): def getLogLevel(self):
try: try:
logLevel = int(utils.settings('logLevel')) logLevel = int(utils.settings('logLevel'))
except ValueError: except ValueError:
logLevel = 0 logLevel = 0
return logLevel return logLevel
def getUserId(self): def getUserId(self):
log = self.logMsg window = utils.window
window = utils.window settings = utils.settings
settings = utils.settings
username = self.getUsername()
username = self.getUsername() w_userId = window('emby_currUser')
w_userId = window('emby_currUser') s_userId = settings('userId%s' % username)
s_userId = settings('userId%s' % username)
# Verify the window property
# Verify the window property if w_userId:
if w_userId: if not s_userId:
if not s_userId: # Save access token if it's missing from settings
# Save access token if it's missing from settings settings('userId%s' % username, value=w_userId)
settings('userId%s' % username, value=w_userId) self.logMsg("Returning userId from WINDOW for username: %s UserId: %s"
log("Returning userId from WINDOW for username: %s UserId: %s" % (username, w_userId), 2)
% (username, w_userId), 2) return w_userId
return w_userId # Verify the settings
# Verify the settings elif s_userId:
elif s_userId: self.logMsg("Returning userId from SETTINGS for username: %s userId: %s"
log("Returning userId from SETTINGS for username: %s userId: %s" % (username, s_userId), 2)
% (username, s_userId), 2) return s_userId
return s_userId # No userId found
# No userId found else:
else: self.logMsg("No userId saved for username: %s." % username, 1)
log("No userId saved for username: %s." % username, 1)
def getServer(self, prefix=True):
def getServer(self, prefix=True):
settings = utils.settings
settings = utils.settings
alternate = settings('altip') == "true"
alternate = settings('altip') == "true" if alternate:
if alternate: # Alternate host
# Alternate host HTTPS = settings('secondhttps') == "true"
HTTPS = settings('secondhttps') == "true" host = settings('secondipaddress')
host = settings('secondipaddress') port = settings('secondport')
port = settings('secondport') else:
else: # Original host
# Original host HTTPS = settings('https') == "true"
HTTPS = settings('https') == "true" host = settings('ipaddress')
host = settings('ipaddress') port = settings('port')
port = settings('port')
server = host + ":" + port
server = host + ":" + port
if not host:
if not host: self.logMsg("No server information saved.", 2)
self.logMsg("No server information saved.", 2) return False
return False
# If https is true
# If https is true if prefix and HTTPS:
if prefix and HTTPS: server = "https://%s" % server
server = "https://%s" % server return server
return server # If https is false
# If https is false elif prefix and not HTTPS:
elif prefix and not HTTPS: server = "http://%s" % server
server = "http://%s" % server return server
return server # If only the host:port is required
# If only the host:port is required elif not prefix:
elif not prefix: return server
return server
def getToken(self):
def getToken(self):
window = utils.window
log = self.logMsg settings = utils.settings
window = utils.window
settings = utils.settings username = self.getUsername()
userId = self.getUserId()
username = self.getUsername() w_token = window('emby_accessToken%s' % userId)
userId = self.getUserId() s_token = settings('accessToken')
w_token = window('emby_accessToken%s' % userId)
s_token = settings('accessToken') # Verify the window property
if w_token:
# Verify the window property if not s_token:
if w_token: # Save access token if it's missing from settings
if not s_token: settings('accessToken', value=w_token)
# Save access token if it's missing from settings self.logMsg("Returning accessToken from WINDOW for username: %s accessToken: %s"
settings('accessToken', value=w_token) % (username, w_token), 2)
log("Returning accessToken from WINDOW for username: %s accessToken: %s" return w_token
% (username, w_token), 2) # Verify the settings
return w_token elif s_token:
# Verify the settings self.logMsg("Returning accessToken from SETTINGS for username: %s accessToken: %s"
elif s_token: % (username, s_token), 2)
log("Returning accessToken from SETTINGS for username: %s accessToken: %s" window('emby_accessToken%s' % username, value=s_token)
% (username, s_token), 2) return s_token
window('emby_accessToken%s' % username, value=s_token) else:
return s_token self.logMsg("No token found.", 1)
else: return ""
log("No token found.", 1)
return "" def getSSLverify(self):
# Verify host certificate
def getSSLverify(self): settings = utils.settings
# Verify host certificate
settings = utils.settings s_sslverify = settings('sslverify')
if settings('altip') == "true":
s_sslverify = settings('sslverify') s_sslverify = settings('secondsslverify')
if settings('altip') == "true":
s_sslverify = settings('secondsslverify') if s_sslverify == "true":
return True
if s_sslverify == "true": else:
return True return False
else:
return False def getSSL(self):
# Client side certificate
def getSSL(self): settings = utils.settings
# Client side certificate
settings = utils.settings s_cert = settings('sslcert')
if settings('altip') == "true":
s_cert = settings('sslcert') s_cert = settings('secondsslcert')
if settings('altip') == "true":
s_cert = settings('secondsslcert') if s_cert == "None":
return None
if s_cert == "None": else:
return None return s_cert
else:
return s_cert def setUserPref(self):
def setUserPref(self): doUtils = self.doUtils.downloadUrl
art = artwork.Artwork()
doUtils = self.doUtils.downloadUrl
art = artwork.Artwork() url = "{server}/emby/Users/{UserId}?format=json"
result = doUtils(url)
url = "{server}/emby/Users/{UserId}?format=json" self.userSettings = result
result = doUtils(url) # Set user image for skin display
self.userSettings = result if result.get('PrimaryImageTag'):
# Set user image for skin display utils.window('EmbyUserImage', value=art.getUserArtwork(result['Id'], 'Primary'))
if result.get('PrimaryImageTag'):
utils.window('EmbyUserImage', value=art.getUserArtwork(result['Id'], 'Primary')) # Set resume point max
url = "{server}/emby/System/Configuration?format=json"
# Set resume point max result = doUtils(url)
url = "{server}/emby/System/Configuration?format=json"
result = doUtils(url) utils.settings('markPlayed', value=str(result['MaxResumePct']))
utils.settings('markPlayed', value=str(result['MaxResumePct'])) def getPublicUsers(self):
def getPublicUsers(self): server = self.getServer()
server = self.getServer() # Get public Users
url = "%s/emby/Users/Public?format=json" % server
# Get public Users result = self.doUtils.downloadUrl(url, authenticate=False)
url = "%s/emby/Users/Public?format=json" % server
result = self.doUtils.downloadUrl(url, authenticate=False) if result != "":
return result
if result != "": else:
return result # Server connection failed
else: return False
# Server connection failed
return False def hasAccess(self):
# hasAccess is verified in service.py
def hasAccess(self): window = utils.window
# hasAccess is verified in service.py
log = self.logMsg url = "{server}/emby/Users?format=json"
window = utils.window result = self.doUtils.downloadUrl(url)
url = "{server}/emby/Users?format=json" if result == False:
result = self.doUtils.downloadUrl(url) # Access is restricted, set in downloadutils.py via exception
self.logMsg("Access is restricted.", 1)
if result == False: self.HasAccess = False
# Access is restricted, set in downloadutils.py via exception
log("Access is restricted.", 1) elif window('emby_online') != "true":
self.HasAccess = False # Server connection failed
pass
elif window('emby_online') != "true":
# Server connection failed elif window('emby_serverStatus') == "restricted":
pass self.logMsg("Access is granted.", 1)
self.HasAccess = True
elif window('emby_serverStatus') == "restricted": window('emby_serverStatus', clear=True)
log("Access is granted.", 1) xbmcgui.Dialog().notification("Emby for Kodi", utils.language(33007))
self.HasAccess = True
window('emby_serverStatus', clear=True) def loadCurrUser(self, authenticated=False):
xbmcgui.Dialog().notification("Emby for Kodi", utils.language(33007))
window = utils.window
def loadCurrUser(self, authenticated=False):
doUtils = self.doUtils
window = utils.window username = self.getUsername()
userId = self.getUserId()
doUtils = self.doUtils
username = self.getUsername() # Only to be used if token exists
userId = self.getUserId() self.currUserId = userId
self.currServer = self.getServer()
# Only to be used if token exists self.currToken = self.getToken()
self.currUserId = userId self.ssl = self.getSSLverify()
self.currServer = self.getServer() self.sslcert = self.getSSL()
self.currToken = self.getToken()
self.ssl = self.getSSLverify() # Test the validity of current token
self.sslcert = self.getSSL() if authenticated == False:
url = "%s/emby/Users/%s?format=json" % (self.currServer, userId)
# Test the validity of current token window('emby_currUser', value=userId)
if authenticated == False: window('emby_accessToken%s' % userId, value=self.currToken)
url = "%s/emby/Users/%s?format=json" % (self.currServer, userId) result = doUtils.downloadUrl(url)
window('emby_currUser', value=userId)
window('emby_accessToken%s' % userId, value=self.currToken) if result == 401:
result = doUtils.downloadUrl(url) # Token is no longer valid
self.resetClient()
if result == 401: return False
# Token is no longer valid
self.resetClient() # Set to windows property
return False window('emby_currUser', value=userId)
window('emby_accessToken%s' % userId, value=self.currToken)
# Set to windows property window('emby_server%s' % userId, value=self.currServer)
window('emby_currUser', value=userId) window('emby_server_%s' % userId, value=self.getServer(prefix=False))
window('emby_accessToken%s' % userId, value=self.currToken)
window('emby_server%s' % userId, value=self.currServer) # Set DownloadUtils values
window('emby_server_%s' % userId, value=self.getServer(prefix=False)) doUtils.setUsername(username)
doUtils.setUserId(self.currUserId)
# Set DownloadUtils values doUtils.setServer(self.currServer)
doUtils.setUsername(username) doUtils.setToken(self.currToken)
doUtils.setUserId(self.currUserId) doUtils.setSSL(self.ssl, self.sslcert)
doUtils.setServer(self.currServer) # parental control - let's verify if access is restricted
doUtils.setToken(self.currToken) self.hasAccess()
doUtils.setSSL(self.ssl, self.sslcert) # Start DownloadUtils session
# parental control - let's verify if access is restricted doUtils.startSession()
self.hasAccess() self.getAdditionalUsers()
# Start DownloadUtils session # Set user preferences in settings
doUtils.startSession() self.currUser = username
self.getAdditionalUsers() self.setUserPref()
# Set user preferences in settings
self.currUser = username
self.setUserPref() def authenticate(self):
lang = utils.language
def authenticate(self): window = utils.window
settings = utils.settings
log = self.logMsg dialog = xbmcgui.Dialog()
lang = utils.language
window = utils.window # Get /profile/addon_data
settings = utils.settings addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8')
dialog = xbmcgui.Dialog() hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
# Get /profile/addon_data username = self.getUsername()
addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8') server = self.getServer()
hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
# If there's no settings.xml
username = self.getUsername() if not hasSettings:
server = self.getServer() self.logMsg("No settings.xml found.", 1)
self.auth = False
# If there's no settings.xml return
if not hasSettings: # If no user information
log("No settings.xml found.", 1) elif not server or not username:
self.auth = False self.logMsg("Missing server information.", 1)
return self.auth = False
# If no user information return
elif not server or not username: # If there's a token, load the user
log("Missing server information.", 1) elif self.getToken():
self.auth = False result = self.loadCurrUser()
return
# If there's a token, load the user if result == False:
elif self.getToken(): pass
result = self.loadCurrUser() else:
self.logMsg("Current user: %s" % self.currUser, 1)
if result == False: self.logMsg("Current userId: %s" % self.currUserId, 1)
pass self.logMsg("Current accessToken: %s" % self.currToken, 2)
else: return
log("Current user: %s" % self.currUser, 1)
log("Current userId: %s" % self.currUserId, 1) ##### AUTHENTICATE USER #####
log("Current accessToken: %s" % self.currToken, 2)
return users = self.getPublicUsers()
password = ""
##### AUTHENTICATE USER #####
# Find user in list
users = self.getPublicUsers() for user in users:
password = "" name = user['Name']
# Find user in list if username.decode('utf-8') in name:
for user in users: # If user has password
name = user['Name'] if user['HasPassword'] == True:
password = dialog.input(
if username.decode('utf-8') in name: heading="%s %s" % (lang(33008), username.decode('utf-8')),
# If user has password option=xbmcgui.ALPHANUM_HIDE_INPUT)
if user['HasPassword'] == True: # If password dialog is cancelled
password = dialog.input( if not password:
heading="%s %s" % (lang(33008), username.decode('utf-8')), self.logMsg("No password entered.", 0)
option=xbmcgui.ALPHANUM_HIDE_INPUT) window('emby_serverStatus', value="Stop")
# If password dialog is cancelled self.auth = False
if not password: return
log("No password entered.", 0) break
window('emby_serverStatus', value="Stop") else:
self.auth = False # Manual login, user is hidden
return password = dialog.input(
break heading="%s %s" % (lang(33008), username),
else: option=xbmcgui.ALPHANUM_HIDE_INPUT)
# Manual login, user is hidden sha1 = hashlib.sha1(password)
password = dialog.input( sha1 = sha1.hexdigest()
heading="%s %s" % (lang(33008), username),
option=xbmcgui.ALPHANUM_HIDE_INPUT) # Authenticate username and password
sha1 = hashlib.sha1(password) url = "%s/emby/Users/AuthenticateByName?format=json" % server
sha1 = sha1.hexdigest() data = {'username': username, 'password': sha1}
self.logMsg(data, 2)
# Authenticate username and password
url = "%s/emby/Users/AuthenticateByName?format=json" % server result = self.doUtils.downloadUrl(url, postBody=data, type="POST", authenticate=False)
data = {'username': username, 'password': sha1}
log(data, 2) try:
self.logMsg("Auth response: %s" % result, 1)
result = self.doUtils.downloadUrl(url, postBody=data, type="POST", authenticate=False) accessToken = result['AccessToken']
try: except (KeyError, TypeError):
log("Auth response: %s" % result, 1) self.logMsg("Failed to retrieve the api key.", 1)
accessToken = result['AccessToken'] accessToken = None
except (KeyError, TypeError): if accessToken is not None:
log("Failed to retrieve the api key.", 1) self.currUser = username
accessToken = None dialog.notification("Emby for Kodi",
"%s %s!" % (lang(33000), self.currUser.decode('utf-8')))
if accessToken is not None: userId = result['User']['Id']
self.currUser = username settings('accessToken', value=accessToken)
dialog.notification("Emby for Kodi", settings('userId%s' % username, value=userId)
"%s %s!" % (lang(33000), self.currUser.decode('utf-8'))) self.logMsg("User Authenticated: %s" % accessToken, 1)
userId = result['User']['Id'] self.loadCurrUser(authenticated=True)
settings('accessToken', value=accessToken) window('emby_serverStatus', clear=True)
settings('userId%s' % username, value=userId) self.retry = 0
log("User Authenticated: %s" % accessToken, 1) else:
self.loadCurrUser(authenticated=True) self.logMsg("User authentication failed.", 1)
window('emby_serverStatus', clear=True) settings('accessToken', value="")
self.retry = 0 settings('userId%s' % username, value="")
else: dialog.ok(lang(33001), lang(33009))
log("User authentication failed.", 1)
settings('accessToken', value="") # Give two attempts at entering password
settings('userId%s' % username, value="") if self.retry == 2:
dialog.ok(lang(33001), lang(33009)) self.logMsg("Too many retries. "
"You can retry by resetting attempts in the addon settings.", 1)
# Give two attempts at entering password window('emby_serverStatus', value="Stop")
if self.retry == 2: dialog.ok(lang(33001), lang(33010))
log("Too many retries. "
"You can retry by resetting attempts in the addon settings.", 1) self.retry += 1
window('emby_serverStatus', value="Stop") self.auth = False
dialog.ok(lang(33001), lang(33010))
def resetClient(self):
self.retry += 1
self.auth = False self.logMsg("Reset UserClient authentication.", 1)
userId = self.getUserId()
def resetClient(self):
if self.currToken is not None:
log = self.logMsg # In case of 401, removed saved token
utils.settings('accessToken', value="")
log("Reset UserClient authentication.", 1) utils.window('emby_accessToken%s' % userId, clear=True)
userId = self.getUserId() self.currToken = None
self.logMsg("User token has been removed.", 1)
if self.currToken is not None:
# In case of 401, removed saved token self.auth = True
utils.settings('accessToken', value="") self.currUser = None
utils.window('emby_accessToken%s' % userId, clear=True)
self.currToken = None def run(self):
log("User token has been removed.", 1)
window = utils.window
self.auth = True
self.currUser = None monitor = xbmc.Monitor()
self.logMsg("----===## Starting UserClient ##===----", 0)
def run(self):
while not monitor.abortRequested():
log = self.logMsg
window = utils.window status = window('emby_serverStatus')
if status:
monitor = xbmc.Monitor() # Verify the connection status to server
log("----===## Starting UserClient ##===----", 0) if status == "restricted":
# Parental control is restricting access
while not monitor.abortRequested(): self.HasAccess = False
status = window('emby_serverStatus') elif status == "401":
if status: # Unauthorized access, revoke token
# Verify the connection status to server window('emby_serverStatus', value="Auth")
if status == "restricted": self.resetClient()
# Parental control is restricting access
self.HasAccess = False if self.auth and (self.currUser is None):
# Try to authenticate user
elif status == "401": status = window('emby_serverStatus')
# Unauthorized access, revoke token if not status or status == "Auth":
window('emby_serverStatus', value="Auth") # Set auth flag because we no longer need
self.resetClient() # to authenticate the user
self.auth = False
if self.auth and (self.currUser is None): self.authenticate()
# Try to authenticate user
status = window('emby_serverStatus')
if not status or status == "Auth": if not self.auth and (self.currUser is None):
# Set auth flag because we no longer need # If authenticate failed.
# to authenticate the user server = self.getServer()
self.auth = False username = self.getUsername()
self.authenticate() status = window('emby_serverStatus')
# The status Stop is for when user cancelled password dialog.
if not self.auth and (self.currUser is None): if server and username and status != "Stop":
# If authenticate failed. # Only if there's information found to login
server = self.getServer() self.logMsg("Server found: %s" % server, 2)
username = self.getUsername() self.logMsg("Username found: %s" % username, 2)
status = window('emby_serverStatus') self.auth = True
# The status Stop is for when user cancelled password dialog.
if server and username and status != "Stop": if self.stopClient == True:
# Only if there's information found to login # If stopping the client didn't work
log("Server found: %s" % server, 2) break
log("Username found: %s" % username, 2)
self.auth = True if monitor.waitForAbort(1):
# Abort was requested while waiting. We should exit
break
if self.stopClient == True:
# If stopping the client didn't work self.doUtils.stopSession()
break self.logMsg("##===---- UserClient Stopped ----===##", 0)
if monitor.waitForAbort(1): def stopClient(self):
# Abort was requested while waiting. We should exit # When emby for kodi terminates
break
self.doUtils.stopSession()
log("##===---- UserClient Stopped ----===##", 0)
def stopClient(self):
# When emby for kodi terminates
self.stopClient = True self.stopClient = True