summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--module/Api.py136
-rw-r--r--module/database/UserDatabase.py22
-rw-r--r--module/plugins/Hook.py12
-rw-r--r--module/remote/RemoteManager.py33
-rw-r--r--module/remote/thriftbackend/Processor.py41
-rw-r--r--module/web/api_app.py6
-rw-r--r--module/web/json_app.py113
-rw-r--r--module/web/pyload_app.py127
-rw-r--r--module/web/templates/default/admin.html50
-rw-r--r--module/web/templates/default/base.html4
-rw-r--r--module/web/utils.py61
-rw-r--r--module/web/webinterface.py1
-rwxr-xr-xpyLoadCore.py2
13 files changed, 277 insertions, 331 deletions
diff --git a/module/Api.py b/module/Api.py
index 37336e568..f4a252ac9 100644
--- a/module/Api.py
+++ b/module/Api.py
@@ -26,14 +26,47 @@ from remote.thriftbackend.thriftgen.pyload.ttypes import *
from remote.thriftbackend.thriftgen.pyload.Pyload import Iface
from PyFile import PyFile
-from database.UserDatabase import ROLE
from utils import freeSpace, compare_time
from common.packagetools import parseNames
from network.RequestFactory import getURL
+# contains function names mapped to their permissions
+# unlisted functions are for admins only
+permMap = {}
+
+# decorator only called on init, never initialized, so has no effect on runtime
+def permission(bits):
+ class _Dec(object):
+ def __new__(cls, func, *args, **kwargs):
+ permMap[func.__name__] = bits
+ return func
+
+ return _Dec
+
+
urlmatcher = re.compile(r"((https?|ftps?|xdcc|sftp):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+-=\\\.&]*)", re.IGNORECASE)
+class PERMS:
+ ALL = 0 # requires no permission, but login
+ ADD = 1 # can add packages
+ DELETE = 2 # can delete packages
+ STATUS = 4 # see and change server status
+ LIST = 16 # see queue and collector
+ MODIFY = 32 # moddify some attribute of downloads
+ DOWNLOAD = 64 # can download from webinterface
+ SETTINGS = 128 # can access settings
+ ACCOUNTS = 256 # can access accounts
+
+class ROLE:
+ ADMIN = 0 #admin has all permissions implicit
+ USER = 1
+
+def has_permission(userperms, perms):
+ # bytewise or perms before if needed
+ return perms == (userperms & perms)
+
+
class Api(Iface):
"""
**pyLoads API**
@@ -75,6 +108,7 @@ class Api(Iface):
section.outline = sub["outline"]
return sections
+ @permission(PERMS.SETTINGS)
def getConfigValue(self, category, option, section="core"):
"""Retrieve config value.
@@ -90,6 +124,7 @@ class Api(Iface):
return str(value) if not isinstance(value, basestring) else value
+ @permission(PERMS.SETTINGS)
def setConfigValue(self, category, option, value, section="core"):
"""Set new config value.
@@ -109,6 +144,7 @@ class Api(Iface):
elif section == "plugin":
self.core.config.setPlugin(category, option, value)
+ @permission(PERMS.SETTINGS)
def getConfig(self):
"""Retrieves complete config of core.
@@ -123,6 +159,7 @@ class Api(Iface):
"""
return self.core.config.config
+ @permission(PERMS.SETTINGS)
def getPluginConfig(self):
"""Retrieves complete config for all plugins.
@@ -138,14 +175,17 @@ class Api(Iface):
return self.core.config.plugin
+ @permission(PERMS.STATUS)
def pauseServer(self):
"""Pause server: Tt wont start any new downloads, but nothing gets aborted."""
self.core.threadManager.pause = True
+ @permission(PERMS.STATUS)
def unpauseServer(self):
"""Unpause server: New Downloads will be started."""
self.core.threadManager.pause = False
+ @permission(PERMS.STATUS)
def togglePause(self):
"""Toggle pause state.
@@ -154,6 +194,7 @@ class Api(Iface):
self.core.threadManager.pause ^= True
return self.core.threadManager.pause
+ @permission(PERMS.STATUS)
def toggleReconnect(self):
"""Toggle reconnect activation.
@@ -162,6 +203,7 @@ class Api(Iface):
self.core.config["reconnect"]["activated"] ^= True
return self.core.config["reconnect"]["activated"]
+ @permission(PERMS.LIST)
def statusServer(self):
"""Some general information about the current status of pyLoad.
@@ -177,10 +219,12 @@ class Api(Iface):
return serverStatus
+ @permission(PERMS.STATUS)
def freeSpace(self):
"""Available free space at download directory in bytes"""
return freeSpace(self.core.config["general"]["download_folder"])
+ @permission(PERMS.ALL)
def getServerVersion(self):
"""pyLoad Core version """
return self.core.version
@@ -194,6 +238,7 @@ class Api(Iface):
pass
#self.core.do_restart = True
+ @permission(PERMS.STATUS)
def getLog(self, offset=0):
"""Returns most recent log entries.
@@ -211,6 +256,7 @@ class Api(Iface):
except:
return ['No log available']
+ @permission(PERMS.STATUS)
def isTimeDownload(self):
"""Checks if pyload will start new downloads according to time in config.
@@ -220,6 +266,7 @@ class Api(Iface):
end = self.core.config['downloadTime']['end'].split(":")
return compare_time(start, end)
+ @permission(PERMS.STATUS)
def isTimeReconnect(self):
"""Checks if pyload will try to make a reconnect
@@ -229,6 +276,7 @@ class Api(Iface):
end = self.core.config['reconnect']['endTime'].split(":")
return compare_time(start, end) and self.core.config["reconnect"]["activated"]
+ @permission(PERMS.LIST)
def statusDownloads(self):
""" Status off all currently running downloads.
@@ -248,6 +296,7 @@ class Api(Iface):
return data
+ @permission(PERMS.ADD)
def addPackage(self, name, links, dest=Destination.Queue):
"""Adds a package, with links to desired destination.
@@ -273,6 +322,7 @@ class Api(Iface):
return pid
+ @permission(PERMS.ADD)
def parseURLs(self, html=None, url=None):
"""Parses html content or any arbitaty text for links and returns result of `checkURLs`
@@ -291,6 +341,7 @@ class Api(Iface):
return self.checkURLs(urls)
+ @permission(PERMS.ADD)
def checkURLs(self, urls):
""" Gets urls and returns pluginname mapped to list of matches urls.
@@ -308,6 +359,7 @@ class Api(Iface):
return plugins
+ @permission(PERMS.ADD)
def checkOnlineStatus(self, urls):
""" initiates online status check
@@ -329,6 +381,7 @@ class Api(Iface):
return OnlineCheck(rid, result)
+ @permission(PERMS.ADD)
def checkOnlineStatusContainer(self, urls, container, data):
""" checks online status of urls and a submited container file
@@ -343,6 +396,7 @@ class Api(Iface):
return self.checkOnlineStatus(urls + [th.name])
+ @permission(PERMS.ADD)
def pollResults(self, rid):
""" Polls the result available for ResultID
@@ -358,6 +412,7 @@ class Api(Iface):
return OnlineCheck(rid, result)
+ @permission(PERMS.ADD)
def generatePackages(self, links):
""" Parses links, generates packages names from urls
@@ -367,6 +422,7 @@ class Api(Iface):
result = parseNames((x, x) for x in links)
return result
+ @permission(PERMS.ADD)
def generateAndAddPackages(self, links, dest=Destination.Queue):
"""Generates and add packages
@@ -377,6 +433,7 @@ class Api(Iface):
return [self.addPackage(name, urls, dest) for name, urls
in self.generatePackages(links).iteritems()]
+ @permission(PERMS.ADD)
def checkAndAddPackages(self, links, dest=Destination.Queue):
"""Checks online status, retrieves names, and will add packages.\
Because of this packages are not added immediatly, only for internal use.
@@ -389,6 +446,7 @@ class Api(Iface):
self.core.threadManager.createResultThread(data, True)
+ @permission(PERMS.LIST)
def getPackageData(self, pid):
"""Returns complete information about package, and included files.
@@ -406,6 +464,7 @@ class Api(Iface):
return pdata
+ @permission(PERMS.LIST)
def getPackageInfo(self, pid):
"""Returns information about package, without detailed information about containing files
@@ -423,6 +482,7 @@ class Api(Iface):
return pdata
+ @permission(PERMS.LIST)
def getFileData(self, fid):
"""Get complete information about a specific file.
@@ -436,6 +496,7 @@ class Api(Iface):
fdata = self._convertPyFile(info.values()[0])
return fdata
+ @permission(PERMS.DELETE)
def deleteFiles(self, fids):
"""Deletes several file entries from pyload.
@@ -446,6 +507,7 @@ class Api(Iface):
self.core.files.save()
+ @permission(PERMS.DELETE)
def deletePackages(self, pids):
"""Deletes packages and containing links.
@@ -456,6 +518,7 @@ class Api(Iface):
self.core.files.save()
+ @permission(PERMS.LIST)
def getQueue(self):
"""Returns info about queue and packages, **not** about files, see `getQueueData` \
or `getPackageData` instead.
@@ -468,6 +531,7 @@ class Api(Iface):
pack["linkstotal"])
for pack in self.core.files.getInfoData(Destination.Queue).itervalues()]
+ @permission(PERMS.LIST)
def getQueueData(self):
"""Return complete data about everything in queue, this is very expensive use it sparely.\
See `getQueue` for alternative.
@@ -480,6 +544,7 @@ class Api(Iface):
links=[self._convertPyFile(x) for x in pack["links"].itervalues()])
for pack in self.core.files.getCompleteData(Destination.Queue).itervalues()]
+ @permission(PERMS.LIST)
def getCollector(self):
"""same as `getQueue` for collector.
@@ -491,6 +556,7 @@ class Api(Iface):
pack["linkstotal"])
for pack in self.core.files.getInfoData(Destination.Collector).itervalues()]
+ @permission(PERMS.LIST)
def getCollectorData(self):
"""same as `getQueueData` for collector.
@@ -503,6 +569,7 @@ class Api(Iface):
for pack in self.core.files.getCompleteData(Destination.Collector).itervalues()]
+ @permission(PERMS.ADD)
def addFiles(self, pid, links):
"""Adds files to specific package.
@@ -514,6 +581,7 @@ class Api(Iface):
self.core.log.info(_("Added %(count)d links to package #%(package)d ") % {"count": len(links), "package": pid})
self.core.files.save()
+ @permission(PERMS.MODIFY)
def pushToQueue(self, pid):
"""Moves package from Collector to Queue.
@@ -521,6 +589,7 @@ class Api(Iface):
"""
self.core.files.setPackageLocation(pid, Destination.Queue)
+ @permission(PERMS.MODIFY)
def pullFromQueue(self, pid):
"""Moves package from Queue to Collector.
@@ -528,6 +597,7 @@ class Api(Iface):
"""
self.core.files.setPackageLocation(pid, Destination.Collector)
+ @permission(PERMS.MODIFY)
def restartPackage(self, pid):
"""Restarts a package, resets every containing files.
@@ -535,6 +605,7 @@ class Api(Iface):
"""
self.core.files.restartPackage(int(pid))
+ @permission(PERMS.MODIFY)
def restartFile(self, fid):
"""Resets file status, so it will be downloaded again.
@@ -542,6 +613,7 @@ class Api(Iface):
"""
self.core.files.restartFile(int(fid))
+ @permission(PERMS.MODIFY)
def recheckPackage(self, pid):
"""Proofes online status of all files in a package, also a default action when package is added.
@@ -550,6 +622,7 @@ class Api(Iface):
"""
self.core.files.reCheckPackage(int(pid))
+ @permission(PERMS.MODIFY)
def stopAllDownloads(self):
"""Aborts all running downloads."""
@@ -557,6 +630,7 @@ class Api(Iface):
for pyfile in pyfiles:
pyfile.abortDownload()
+ @permission(PERMS.MODIFY)
def stopDownloads(self, fids):
"""Aborts specific downloads.
@@ -569,6 +643,7 @@ class Api(Iface):
if pyfile.id in fids:
pyfile.abortDownload()
+ @permission(PERMS.MODIFY)
def setPackageName(self, pid, name):
"""Renames a package.
@@ -579,6 +654,7 @@ class Api(Iface):
pack.name = name
pack.sync()
+ @permission(PERMS.MODIFY)
def movePackage(self, destination, pid):
"""Set a new package location.
@@ -588,6 +664,7 @@ class Api(Iface):
if destination not in (0, 1): return
self.core.files.setPackageLocation(pid, destination)
+ @permission(PERMS.MODIFY)
def moveFiles(self, fids, pid):
"""Move multiple files to another package
@@ -599,6 +676,7 @@ class Api(Iface):
pass
+ @permission(PERMS.ADD)
def uploadContainer(self, filename, data):
"""Uploads and adds a container file to pyLoad.
@@ -611,6 +689,7 @@ class Api(Iface):
self.addPackage(th.name, [th.name], Destination.Queue)
+ @permission(PERMS.MODIFY)
def orderPackage(self, pid, position):
"""Gives a package a new position.
@@ -619,6 +698,7 @@ class Api(Iface):
"""
self.core.files.reorderPackage(pid, position)
+ @permission(PERMS.MODIFY)
def orderFile(self, fid, position):
"""Gives a new position to a file within its package.
@@ -627,6 +707,7 @@ class Api(Iface):
"""
self.core.files.reorderFile(fid, position)
+ @permission(PERMS.MODIFY)
def setPackageData(self, pid, data):
"""Allows to modify several package attributes.
@@ -643,6 +724,7 @@ class Api(Iface):
p.sync()
self.core.files.save()
+ @permission(PERMS.DELETE)
def deleteFinished(self):
"""Deletes all finished files and completly finished packages.
@@ -651,10 +733,12 @@ class Api(Iface):
deleted = self.core.files.deleteFinishedLinks()
return deleted
+ @permission(PERMS.MODIFY)
def restartFailed(self):
"""Restarts all failed failes."""
self.core.files.restartFailed()
+ @permission(PERMS.LIST)
def getPackageOrder(self, destination):
"""Returns information about package order.
@@ -672,6 +756,7 @@ class Api(Iface):
order[pack["order"]] = pack["id"]
return order
+ @permission(PERMS.LIST)
def getFileOrder(self, pid):
"""Information about file order within package.
@@ -687,6 +772,7 @@ class Api(Iface):
return order
+ @permission(PERMS.STATUS)
def isCaptchaWaiting(self):
"""Indicates wether a captcha task is available
@@ -696,6 +782,7 @@ class Api(Iface):
task = self.core.captchaManager.getTask()
return not task is None
+ @permission(PERMS.STATUS)
def getCaptchaTask(self, exclusive=False):
"""Returns a captcha task
@@ -712,6 +799,7 @@ class Api(Iface):
else:
return CaptchaTask(-1)
+ @permission(PERMS.STATUS)
def getCaptchaTaskStatus(self, tid):
"""Get information about captcha task
@@ -722,6 +810,7 @@ class Api(Iface):
t = self.core.captchaManager.getTaskByID(tid)
return t.getStatus() if t else ""
+ @permission(PERMS.STATUS)
def setCaptchaResult(self, tid, result):
"""Set result for a captcha task
@@ -735,6 +824,7 @@ class Api(Iface):
self.core.captchaManager.removeTask(task)
+ @permission(PERMS.STATUS)
def getEvents(self, uuid):
"""Lists occured events, may be affected to changes in future.
@@ -764,6 +854,7 @@ class Api(Iface):
newEvents.append(event)
return newEvents
+ @permission(PERMS.ACCOUNTS)
def getAccounts(self, refresh):
"""Get information about all entered accounts.
@@ -778,6 +869,7 @@ class Api(Iface):
for acc in group])
return accounts
+ @permission(PERMS.ALL)
def getAccountTypes(self):
"""All available account types.
@@ -785,10 +877,12 @@ class Api(Iface):
"""
return self.core.accountManager.accounts.keys()
+ @permission(PERMS.ACCOUNTS)
def updateAccount(self, plugin, account, password=None, options={}):
"""Changes pw/options for specific account."""
self.core.accountManager.updateAccount(plugin, account, password, options)
+ @permission(PERMS.ACCOUNTS)
def removeAccount(self, plugin, account):
"""Remove account from pyload.
@@ -797,6 +891,7 @@ class Api(Iface):
"""
self.core.accountManager.removeAccount(plugin, account)
+ @permission(PERMS.ALL)
def login(self, username, password, remoteip=None):
"""Login into pyLoad, this **must** be called when using rpc before any methods can be used.
@@ -805,26 +900,38 @@ class Api(Iface):
:param remoteip: Omit this argument, its only used internal
:return: bool indicating login was successful
"""
- if self.core.config["remote"]["nolocalauth"] and remoteip == "127.0.0.1":
- return True
- if self.core.startedInGui and remoteip == "127.0.0.1":
- return True
+ return True if self.checkAuth(username, password, remoteip) else False
- user = self.core.db.checkAuth(username, password)
- if user and user["role"] == ROLE.ADMIN:
- return True
-
- return False
-
- def checkAuth(self, username, password):
+ def checkAuth(self, username, password, remoteip=None):
"""Check authentication and returns details
:param username:
:param password:
+ :param remoteip:
:return: dict with info, empty when login is incorrect
"""
+ if self.core.config["remote"]["nolocalauth"] and remoteip == "127.0.0.1":
+ return "local"
+ if self.core.startedInGui and remoteip == "127.0.0.1":
+ return "local"
+
return self.core.db.checkAuth(username, password)
+ def isAuthorized(self, func, userdata):
+ """checks if the user is authorized for specific method
+
+ :param func: function name
+ :param userdata: dictionary of user data
+ :return: boolean
+ """
+ if userdata["role"] == ROLE.ADMIN or userdata == "local":
+ return True
+ elif func in permMap and has_permission(userdata["permission"], permMap[func]):
+ return True
+ else:
+ return False
+
+
def getUserData(self, username, password):
"""see `checkAuth`"""
return self.checkAuth(username, password)
@@ -834,6 +941,7 @@ class Api(Iface):
"""returns all known user and info"""
return self.core.db.getAllUserData()
+ @permission(PERMS.STATUS)
def getServices(self):
""" A dict of available services, these can be defined by hook plugins.
@@ -845,6 +953,7 @@ class Api(Iface):
return data
+ @permission(PERMS.STATUS)
def hasService(self, plugin, func):
"""Checks wether a service is available.
@@ -855,6 +964,7 @@ class Api(Iface):
cont = self.core.hookManager.methods
return plugin in cont and func in cont[plugin]
+ @permission(PERMS.STATUS)
def call(self, info):
"""Calls a service (a method in hook plugin).
@@ -877,6 +987,7 @@ class Api(Iface):
except Exception, e:
raise ServiceException(e.message)
+ @permission(PERMS.STATUS)
def getAllInfo(self):
"""Returns all information stored by hook plugins. Values are always strings
@@ -884,6 +995,7 @@ class Api(Iface):
"""
return self.core.hookManager.getAllInfo()
+ @permission(PERMS.STATUS)
def getInfoByPlugin(self, plugin):
"""Returns information stored by a specific plugin.
diff --git a/module/database/UserDatabase.py b/module/database/UserDatabase.py
index f888e219e..e74399c11 100644
--- a/module/database/UserDatabase.py
+++ b/module/database/UserDatabase.py
@@ -16,29 +16,11 @@
@author: mkaay
"""
-from DatabaseBackend import DatabaseBackend
-from DatabaseBackend import style
-
from hashlib import sha1
import random
-class PERMS:
- ALL = 0 # requires no permission, but login
- ADD = 1 # can add packages
- DELETE = 2 # can delete packages
- STATUS = 4 # see and change server status
- SEE_DOWNLOADS = 16 # see queue and collector / modify downloads
- DOWNLOAD = 32 # can download from webinterface
- SETTINGS = 64 # can access settings
- ACCOUNTS = 128 # can access accounts
-
-class ROLE:
- ADMIN = 0 #admin has all permissions implicit
- USER = 1
-
-def has_permission(current, perms):
- # bytewise or perms before if needed
- return perms == (current & perms)
+from DatabaseBackend import DatabaseBackend
+from DatabaseBackend import style
class UserMethods():
@style.queue
diff --git a/module/plugins/Hook.py b/module/plugins/Hook.py
index 7e4f58c66..85fb49190 100644
--- a/module/plugins/Hook.py
+++ b/module/plugins/Hook.py
@@ -23,16 +23,10 @@ from traceback import print_exc
class Expose(object):
""" used for decoration to declare rpc services """
- def __init__(self, *args, **kwargs):
- self._f = args[0]
- hookManager.addRPC(self._f.__module__, self._f.func_name, self._f.func_doc)
- def __get__(self, obj, klass):
- self._obj = obj
- return self
-
- def __call__(self, *args, **kwargs):
- return self._f(self._obj, *args, **kwargs)
+ def __new__(cls, f, *args, **kwargs):
+ hookManager.addRPC(f.__module__, f.func_name, f.func_doc)
+ return f
def threaded(f):
def run(*args,**kwargs):
diff --git a/module/remote/RemoteManager.py b/module/remote/RemoteManager.py
index 792eaec4d..2ac26a677 100644
--- a/module/remote/RemoteManager.py
+++ b/module/remote/RemoteManager.py
@@ -19,14 +19,12 @@
from threading import Thread
from traceback import print_exc
-from module.database.UserDatabase import ROLE
-
class BackendBase(Thread):
def __init__(self, manager):
Thread.__init__(self)
- self.manager = manager
+ self.m = manager
self.core = manager.core
-
+
def run(self):
try:
self.serve()
@@ -34,18 +32,16 @@ class BackendBase(Thread):
self.core.log.error(_("Remote backend error: %s") % e)
if self.core.debug:
print_exc()
-
+
def setup(self, host, port):
pass
-
+
def checkDeps(self):
return True
-
+
def serve(self):
pass
-
- def checkAuth(self, user, password, remoteip=None):
- return self.manager.checkAuth(user, password, remoteip)
+
class RemoteManager():
available = ["ThriftBackend"]
@@ -53,14 +49,13 @@ class RemoteManager():
def __init__(self, core):
self.core = core
self.backends = []
-
- def startBackends(self):
+ def startBackends(self):
host = self.core.config["remote"]["listenaddr"]
port = self.core.config["remote"]["port"]
for b in self.available:
- klass = getattr(__import__("module.remote.%s" % b, globals(), locals(), [b] , -1), b)
+ klass = getattr(__import__("module.remote.%s" % b, globals(), locals(), [b], -1), b)
backend = klass(self)
if not backend.checkDeps():
continue
@@ -76,15 +71,3 @@ class RemoteManager():
self.backends.append(backend)
port += 1
-
- def checkAuth(self, user, password, remoteip=None):
- if self.core.config["remote"]["nolocalauth"] and remoteip == "127.0.0.1":
- return True
- if self.core.startedInGui and remoteip == "127.0.0.1":
- return True
-
- user = self.core.db.checkAuth(user, password)
- if user and user["role"] == ROLE.ADMIN:
- return user
- else:
- return {}
diff --git a/module/remote/thriftbackend/Processor.py b/module/remote/thriftbackend/Processor.py
index a8fc94298..a8b87c82c 100644
--- a/module/remote/thriftbackend/Processor.py
+++ b/module/remote/thriftbackend/Processor.py
@@ -12,14 +12,18 @@ class Processor(Pyload.Processor):
if trans not in self.authenticated:
self.authenticated[trans] = False
oldclose = trans.close
+
def wrap():
if self in self.authenticated:
del self.authenticated[trans]
oldclose()
+
trans.close = wrap
authenticated = self.authenticated[trans]
(name, type, seqid) = iprot.readMessageBegin()
- if name not in self._processMap or (not authenticated and not name == "login"):
+
+ # unknown method
+ if name not in self._processMap:
iprot.skip(Pyload.TType.STRUCT)
iprot.readMessageEnd()
x = Pyload.TApplicationException(Pyload.TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % name)
@@ -28,17 +32,46 @@ class Processor(Pyload.Processor):
oprot.writeMessageEnd()
oprot.trans.flush()
return
+
+ # not logged in
+ elif not authenticated and not name == "login":
+ iprot.skip(Pyload.TType.STRUCT)
+ iprot.readMessageEnd()
+ # 20 - Not logged in (in situ declared error code)
+ x = Pyload.TApplicationException(20, 'Not logged in')
+ oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
+ x.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+ return
+
elif not authenticated and name == "login":
args = Pyload.login_args()
args.read(iprot)
iprot.readMessageEnd()
result = Pyload.login_result()
- self.authenticated[trans] = self._handler.login(args.username, args.password, trans.remoteaddr[0])
- result.success = self.authenticated[trans]
+ # api login
+ self.authenticated[trans] = self._handler.checkAuth(args.username, args.password, trans.remoteaddr[0])
+
+ result.success = True if self.authenticated[trans] else False
oprot.writeMessageBegin("login", Pyload.TMessageType.REPLY, seqid)
result.write(oprot)
oprot.writeMessageEnd()
oprot.trans.flush()
- else:
+
+ elif self._handler.isAuthorized(name, authenticated):
self._processMap[name](self, seqid, iprot, oprot)
+
+ else:
+ #no permission
+ iprot.skip(Pyload.TType.STRUCT)
+ iprot.readMessageEnd()
+ # 21 - Not authorized
+ x = Pyload.TApplicationException(21, 'Not authorized')
+ oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid)
+ x.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+ return
+
return True
diff --git a/module/web/api_app.py b/module/web/api_app.py
index 32b128e6a..156922d6a 100644
--- a/module/web/api_app.py
+++ b/module/web/api_app.py
@@ -14,7 +14,6 @@ from utils import toDict, set_session
from webinterface import PYLOAD
from module.common.json_layer import json_dumps
-from module.database.UserDatabase import ROLE
try:
from ast import literal_eval
@@ -46,9 +45,12 @@ def call_api(func, args=""):
if 'session' in request.POST:
s = s.get_by_id(request.POST['session'])
- if not s or not s.get("authenticated", False) or s.get("role", -1) != ROLE.ADMIN:
+ if not s or not s.get("authenticated", False):
return HTTPError(401, json_dumps("Unauthorized"))
+ if not PYLOAD.isAuthorized(func, {"role": s["role"], "permission": s["perms"]}):
+ return HTTPError(403, json_dumps("Forbidden"))
+
args = args.split("/")[1:]
kwargs = {}
diff --git a/module/web/json_app.py b/module/web/json_app.py
index 0573eff77..6d50525bb 100644
--- a/module/web/json_app.py
+++ b/module/web/json_app.py
@@ -31,7 +31,7 @@ def get_sort_key(item):
@route("/json/status")
@route("/json/status", method="POST")
-@login_required('see_downloads')
+@login_required('LIST')
def status():
try:
status = toDict(PYLOAD.statusServer())
@@ -43,7 +43,7 @@ def status():
@route("/json/links")
@route("/json/links", method="POST")
-@login_required('see_downloads')
+@login_required('LIST')
def links():
try:
links = [toDict(x) for x in PYLOAD.statusDownloads()]
@@ -69,7 +69,7 @@ def links():
@route("/json/queue")
-@login_required('see_downloads')
+@login_required('LIST')
def queue():
print "/json/queue"
try:
@@ -80,7 +80,7 @@ def queue():
@route("/json/pause")
-@login_required('status')
+@login_required('STATUS')
def pause():
try:
return PYLOAD.pauseServer()
@@ -90,7 +90,7 @@ def pause():
@route("/json/unpause")
-@login_required('status')
+@login_required('STATUS')
def unpause():
try:
return PYLOAD.unpauseServer()
@@ -100,7 +100,7 @@ def unpause():
@route("/json/cancel")
-@login_required('status')
+@login_required('STATUS')
def cancel():
try:
return PYLOAD.stopAllDownloads()
@@ -109,7 +109,7 @@ def cancel():
@route("/json/packages")
-@login_required('see_downloads')
+@login_required('LIST')
def packages():
print "/json/packages"
try:
@@ -128,7 +128,7 @@ def packages():
@route("/json/package/:id")
@validate(id=int)
-@login_required('see_downloads')
+@login_required('LIST')
def package(id):
try:
data = toDict(PYLOAD.getPackageData(id))
@@ -163,7 +163,7 @@ def package(id):
@route("/json/package_order/:ids")
-@login_required('add')
+@login_required('ADD')
def package_order(ids):
try:
pid, pos = ids.split("|")
@@ -175,7 +175,7 @@ def package_order(ids):
@route("/json/link/:id")
@validate(id=int)
-@login_required('see_downloads')
+@login_required('LIST')
def link(id):
print "/json/link/%d" % id
try:
@@ -187,7 +187,7 @@ def link(id):
@route("/json/remove_link/:id")
@validate(id=int)
-@login_required('delete')
+@login_required('DELETE')
def remove_link(id):
try:
PYLOAD.deleteFiles([id])
@@ -198,7 +198,7 @@ def remove_link(id):
@route("/json/restart_link/:id")
@validate(id=int)
-@login_required('add')
+@login_required('ADD')
def restart_link(id):
try:
PYLOAD.restartFile(id)
@@ -209,7 +209,7 @@ def restart_link(id):
@route("/json/abort_link/:id")
@validate(id=int)
-@login_required('delete')
+@login_required('DELETE')
def abort_link(id):
try:
PYLOAD.stopDownloads([id])
@@ -219,7 +219,7 @@ def abort_link(id):
@route("/json/link_order/:ids")
-@login_required('add')
+@login_required('ADD')
def link_order(ids):
try:
pid, pos = ids.split("|")
@@ -231,7 +231,7 @@ def link_order(ids):
@route("/json/add_package")
@route("/json/add_package", method="POST")
-@login_required('add')
+@login_required('ADD')
def add_package():
name = request.forms.get("add_name", "New Package").strip()
queue = int(request.forms['add_dest'])
@@ -267,7 +267,7 @@ def add_package():
@route("/json/remove_package/:id")
@validate(id=int)
-@login_required('delete')
+@login_required('DELETE')
def remove_package(id):
try:
PYLOAD.deletePackages([id])
@@ -278,7 +278,7 @@ def remove_package(id):
@route("/json/restart_package/:id")
@validate(id=int)
-@login_required('add')
+@login_required('MODIFY')
def restart_package(id):
try:
PYLOAD.restartPackage(id)
@@ -290,7 +290,7 @@ def restart_package(id):
@route("/json/move_package/:dest/:id")
@validate(dest=int, id=int)
-@login_required('add')
+@login_required('MODIFY')
def move_package(dest, id):
try:
PYLOAD.movePackage(dest, id)
@@ -300,7 +300,7 @@ def move_package(dest, id):
@route("/json/edit_package", method="POST")
-@login_required('add')
+@login_required('MODIFY')
def edit_package():
try:
id = int(request.forms.get("pack_id"))
@@ -317,7 +317,7 @@ def edit_package():
@route("/json/set_captcha")
@route("/json/set_captcha", method="POST")
-@login_required('add')
+@login_required('ADD')
def set_captcha():
if request.environ.get('REQUEST_METHOD', "GET") == "POST":
try:
@@ -336,13 +336,13 @@ def set_captcha():
@route("/json/delete_finished")
-@login_required('delete')
+@login_required('DELETE')
def delete_finished():
return {"del": PYLOAD.deleteFinished()}
@route("/json/restart_failed")
-@login_required('delete')
+@login_required('MODIFY')
def restart_failed():
restart = PYLOAD.restartFailed()
@@ -351,7 +351,7 @@ def restart_failed():
@route("/json/load_config/:category/:section")
-@login_required("settings")
+@login_required("SETTINGS")
def load_config(category, section):
conf = None
if category == "general":
@@ -371,7 +371,7 @@ def load_config(category, section):
@route("/json/save_config/:category", method="POST")
-@login_required("settings")
+@login_required("SETTINGS")
def save_config(category):
for key, value in request.POST.iteritems():
try:
@@ -385,7 +385,7 @@ def save_config(category):
@route("/json/add_account", method="POST")
-@login_required("settings")
+@login_required("ACCOUNTS")
def add_account():
login = request.POST["account_login"]
password = request.POST["account_password"]
@@ -395,7 +395,7 @@ def add_account():
@route("/json/update_accounts", method="POST")
-@login_required("settings")
+@login_required("ACCOUNTS")
def update_accounts():
deleted = [] #dont update deleted accs or they will be created again
@@ -428,64 +428,3 @@ def change_password():
if not PYLOAD.changePassword(user, oldpw, newpw):
print "Wrong password"
return HTTPError()
-
-#@route("/json/filemanager/rename", method="POST")
-#@login_required('filemanager')
-def rename_dir():
- try:
- path = decode(request.forms.get("path"))
- old_name = path + "/" + decode(request.forms.get("old_name"))
- new_name = path + "/" + decode(request.forms.get("new_name"))
-
- try:
- #check if file exists
- os.rename(old_name, new_name)
- except Exception, e:
- return {"response": "fail", "error": str(e) + "\n" + old_name + " => " + new_name}
-
- return {"response": "success"}
-
- except:
- return HTTPError()
-
-
-#@route("/json/filemanager/delete", method="POST")
-#@login_required('filemanager')
-def delete_dir():
- try:
- try:
- path = decode(request.forms.get("path"))
- name = decode(request.forms.get("name"))
- shutil.rmtree(path + "/" + name)
- except Exception, e:
- return {"response": "fail", "error": str(e) + "\n" + path + "/" + name}
-
- return {"response": "success"}
-
- except:
- return HTTPError()
-
-
-#@route("/json/filemanager/mkdir", method="POST")
-#@login_required('filemanager')
-def make_dir():
- try:
- path = decode(request.forms.get("path"))
- name = decode(request.forms.get("name"))
- try:
- #i = 1
- #full_name = path + "/" + name
- #while os.path.exists(full_name)
- # full_name = full_name + i
- # i = i + 1
- #
- #os.mkdir(full_name)
-
- os.mkdir(path + "/" + name)
- except Exception, e:
- return {"response": "fail", "error": str(e) + "\nUnable to create directory: " + path + "/" + name}
-
- return {"response": "success", "path": path, "name": name}
-
- except:
- return HTTPError()
diff --git a/module/web/pyload_app.py b/module/web/pyload_app.py
index 49568baad..553eeaa57 100644
--- a/module/web/pyload_app.py
+++ b/module/web/pyload_app.py
@@ -32,11 +32,11 @@ from bottle import route, static_file, request, response, redirect, HTTPError, e
from webinterface import PYLOAD, PYLOAD_DIR, PROJECT_DIR, SETUP
from utils import render_to_response, parse_permissions, parse_userdata, \
- login_required, get_permission, set_permission, toDict, set_session
+ login_required, get_permission, set_permission, permlist, toDict, set_session
from filters import relpath, unquotepath
-from module.utils import formatSize, decode, fs_decode
+from module.utils import formatSize, fs_decode
# Helper
@@ -132,7 +132,7 @@ def logout():
@route("/")
@route("/home")
-@login_required("see_downloads")
+@login_required("LIST")
def home():
try:
res = [toDict(x) for x in PYLOAD.statusDownloads()]
@@ -149,7 +149,7 @@ def home():
@route("/queue")
-@login_required("see_downloads")
+@login_required("LIST")
def queue():
queue = PYLOAD.getQueue()
@@ -159,7 +159,7 @@ def queue():
@route("/collector")
-@login_required('see_downloads')
+@login_required('LIST')
def collector():
queue = PYLOAD.getCollector()
@@ -169,7 +169,7 @@ def collector():
@route("/downloads")
-@login_required('download')
+@login_required('DOWNLOAD')
def downloads():
root = fs_decode(PYLOAD.getConfigValue("general", "download_folder"))
@@ -205,7 +205,7 @@ def downloads():
@route("/downloads/get/:path#.+#")
-@login_required("download")
+@login_required("DOWNLOAD")
def get_download(path):
path = unquote(path)
#@TODO some files can not be downloaded
@@ -221,64 +221,9 @@ def get_download(path):
return HTTPError(404, "File not Found.")
-#@route("/filemanager")
-#@login_required('filemanager')
-def filemanager():
- root = PYLOAD.getConfigValue("general", "download_folder")
-
- if not isdir(root):
- return base([_('Download directory not found.')])
-
- root_node = {'name': '/',
- 'path': root,
- 'files': [],
- 'folders': []
- }
-
- for item in sorted(listdir(root)):
- if isdir(join(root, item)):
- root_node['folders'].append(iterate_over_dir(root, item))
- elif isfile(join(root, item)):
- f = {
- 'name': decode(item),
- 'path': root
- }
- root_node['files'].append(f)
-
- return render_to_response('filemanager.html', {'root': root_node}, [pre_processor])
-
-
-def iterate_over_dir(root, dir):
- out = {
- 'name': decode(dir),
- 'path': root,
- 'files': [],
- 'folders': []
- }
- for item in sorted(listdir(join(root, dir))):
- subroot = join(root, dir)
- if isdir(join(subroot, item)):
- out['folders'].append(iterate_over_dir(subroot, item))
- elif isfile(join(subroot, item)):
- f = {
- 'name': decode(item),
- 'path': subroot
- }
- out['files'].append(f)
-
- return out
-
-
-#@route("/filemanager/get_dir", "POST")
-#@login_required('filemanager')
-def folder():
- path = request.forms.get("path").decode("utf8", "ignore")
- name = request.forms.get("name").decode("utf8", "ignore")
- return render_to_response('folder.html', {'path': path, 'name': name}, [pre_processor])
-
@route("/settings")
-@login_required('settings')
+@login_required('SETTINGS')
def config():
conf = PYLOAD.getConfig()
plugin = PYLOAD.getPluginConfig()
@@ -327,7 +272,7 @@ def config():
@route("/package_ui.js")
-@login_required('see_downloads')
+@login_required('LIST')
def package_ui():
response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
time.gmtime(time.time() + 60 * 60 * 24 * 7))
@@ -336,7 +281,7 @@ def package_ui():
@route("/filemanager_ui.js")
-@login_required('see_downloads')
+@login_required('LIST')
def package_ui():
response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
time.gmtime(time.time() + 60 * 60 * 24 * 7))
@@ -348,7 +293,7 @@ def package_ui():
@route("/pathchooser")
@route("/filechooser/:file#.+#")
@route("/pathchooser/:path#.+#")
-@login_required('status')
+@login_required('STATUS')
def path(file="", path=""):
if file:
type = "file"
@@ -438,7 +383,7 @@ def path(file="", path=""):
@route("/logs", method="POST")
@route("/logs/:item")
@route("/logs/:item", method="POST")
-@login_required('status')
+@login_required('STATUS')
def logs(item=-1):
s = request.environ.get('beaker.session')
@@ -523,14 +468,17 @@ def logs(item=-1):
@route("/admin")
@route("/admin", method="POST")
-@login_required("is_admin")
+@login_required("ADMIN")
def admin():
user = PYLOAD.getAllUserData()
+ perms = permlist()
+
for data in user.itervalues():
data["perms"] = {}
get_permission(data["perms"], data["permission"])
data["perms"]["admin"] = True if data["role"] is 0 else False
+
s = request.environ.get('beaker.session')
if request.environ.get('REQUEST_METHOD', "GET") == "POST":
for name in user:
@@ -541,46 +489,19 @@ def admin():
user[name]["role"] = 1
user[name]["perms"]["admin"] = False
- if request.POST.get("%s|add" % name, False):
- user[name]["perms"]["add"] = True
- else:
- user[name]["perms"]["add"] = False
-
- if request.POST.get("%s|delete" % name, False):
- user[name]["perms"]["delete"] = True
- else:
- user[name]["perms"]["delete"] = False
-
- if request.POST.get("%s|status" % name, False):
- user[name]["perms"]["status"] = True
- else:
- user[name]["perms"]["status"] = False
-
- if request.POST.get("%s|see_downloads" % name, False):
- user[name]["perms"]["see_downloads"] = True
- else:
- user[name]["perms"]["see_downloads"] = False
-
- if request.POST.get("%s|download" % name, False):
- user[name]["perms"]["download"] = True
- else:
- user[name]["perms"]["download"] = False
-
- if request.POST.get("%s|settings" % name, False):
- user[name]["perms"]["settings"] = True
- else:
- user[name]["perms"]["settings"] = False
-
- if request.POST.get("%s|filemanager" % name, False):
- user[name]["perms"]["filemanager"] = True
- else:
- user[name]["perms"]["filemanager"] = False
+ # set all perms to false
+ for perm in perms:
+ user[name]["perms"][perm] = False
+
+
+ for perm in request.POST.getall("%s|perms" % name):
+ user[name]["perms"][perm] = True
user[name]["permission"] = set_permission(user[name]["perms"])
PYLOAD.setUserPermission(name, user[name]["permission"], user[name]["role"])
- return render_to_response("admin.html", {"users": user}, [pre_processor])
+ return render_to_response("admin.html", {"users": user, "permlist": perms}, [pre_processor])
@route("/setup")
diff --git a/module/web/templates/default/admin.html b/module/web/templates/default/admin.html
index 7b9a8b32d..96c5e7ef3 100644
--- a/module/web/templates/default/admin.html
+++ b/module/web/templates/default/admin.html
@@ -72,9 +72,8 @@
{% block content %}
- {{ _("Note: You can only change permissions for webinterface.") }}
{{ _("To add user or change passwords use:") }} <b>python pyLoadCore.py -u</b><br>
- {{ _("Important: Admin user have always all permissions! Only Admin user can use other clients like CLI and GUI.") }}
+ {{ _("Important: Admin user have always all permissions!") }}
<form action="" method="POST">
<table class="settable wide">
@@ -89,48 +88,27 @@
{{ _("Admin") }}
</th>
<th>
- {{ _("Add downloads") }}
- </th>
- <th>
- {{ _("Delete downloads") }}
- </th>
- <th>
- {{ _("Change server status") }}
- </th>
- <th>
- {{ _("See queue/collector") }}
- </th>
- <th>
- {{ _("Download from webinterface") }}
- </th>
- <th>
- {{ _("Change settings") }}
- </th>
- <th>
- {{ _("Filemanager") }}
+ {{ _("Permissions") }}
</th>
</thead>
- {% for name, data in users.iteritems ( ) %}
+ {% for name, data in users.iteritems() %}
<tr>
<td>{{ name }}</td>
<td><a class="change_password" href="#" id="change_pw|{{name}}">{{ _("change") }}</a></td>
<td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %}
checked="True" {% endif %}"></td>
- <td><input name="{{ name }}|add" type="checkbox" {% if data.perms.add %} checked="True" {% endif %}
- "></td>
- <td><input name="{{ name }}|delete" type="checkbox" {% if data.perms.delete %}
- checked="True" {% endif %}"></td>
- <td><input name="{{ name }}|status" type="checkbox" {% if data.perms.status %}
- checked="True" {% endif %}"></td>
- <td><input name="{{ name }}|see_downloads" type="checkbox" {% if data.perms.see_downloads %}
- checked="True" {% endif %}"></td>
- <td><input name="{{ name }}|download" type="checkbox" {% if data.perms.download %}
- checked="True" {% endif %}"></td>
- <td><input name="{{ name }}|settings" type="checkbox" {% if data.perms.settings %}
- checked="True" {% endif %}"></td>
- <td><input name="{{ name }}|filemanager" type="checkbox" {% if data.perms.filemanager %}
- checked="True" {% endif %}"></td>
+ <td>
+ <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms">
+ {% for perm in permlist %}
+ {% if data.perms|getitem(perm) %}
+ <option selected="selected">{{ perm }}</option>
+ {% else %}
+ <option>{{ perm }}</option>
+ {% endif %}
+ {% endfor %}
+ </select>
+ </td>
</tr>
{% endfor %}
diff --git a/module/web/templates/default/base.html b/module/web/templates/default/base.html
index 4057d320c..2987cf081 100644
--- a/module/web/templates/default/base.html
+++ b/module/web/templates/default/base.html
@@ -257,7 +257,7 @@ function AddBox(){
<div style="clear:both;"></div>
</div>
-{% if perms.status %}
+{% if perms.STATUS %}
<ul id="page-actions2">
<li id="action_play"><a href="#" class="action play" accesskey="o" rel="nofollow">{{_("Start")}}</a></li>
<li id="action_stop"><a href="#" class="action stop" accesskey="o" rel="nofollow">{{_("Stop")}}</a></li>
@@ -266,7 +266,7 @@ function AddBox(){
</ul>
{% endif %}
-{% if perms.see_downloads %}
+{% if perms.LIST %}
<ul id="page-actions">
<li><span class="time">{{_("Download:")}}</span><a id="time" style=" background-color: {% if status.download %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
<li><span class="reconnect">{{_("Reconnect:")}}</span><a id="reconnect" style=" background-color: {% if status.reconnect %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
diff --git a/module/web/utils.py b/module/web/utils.py
index 39ddb361f..a89c87558 100644
--- a/module/web/utils.py
+++ b/module/web/utils.py
@@ -20,7 +20,7 @@ from bottle import request, HTTPError, redirect, ServerAdapter
from webinterface import env, TEMPLATE
-from module.database.UserDatabase import has_permission, PERMS, ROLE
+from module.Api import has_permission, PERMS, ROLE
def render_to_response(name, args={}, proc=[]):
for p in proc:
@@ -29,15 +29,11 @@ def render_to_response(name, args={}, proc=[]):
t = env.get_template(TEMPLATE + "/" + name)
return t.render(**args)
+
def parse_permissions(session):
- perms = {"add": False,
- "delete": False,
- "status": False,
- "see_downloads": False,
- "download" : False,
- "filemanager" : False,
- "settings": False,
- "is_admin": False}
+ perms = dict([(x, False) for x in dir(PERMS) if not x.startswith("_")])
+ perms["ADMIN"] = False
+ perms["is_admin"] = False
if not session.get("authenticated", False):
return perms
@@ -53,31 +49,31 @@ def parse_permissions(session):
return perms
+def permlist():
+ return [x for x in dir(PERMS) if not x.startswith("_") and x != "ALL"]
+
+
def get_permission(perms, p):
- perms["add"] = has_permission(p, PERMS.ADD)
- perms["delete"] = has_permission(p, PERMS.DELETE)
- perms["status"] = has_permission(p, PERMS.STATUS)
- perms["see_downloads"] = has_permission(p, PERMS.SEE_DOWNLOADS)
- perms["download"] = has_permission(p, PERMS.DOWNLOAD)
- perms["settings"] = has_permission(p, PERMS.SETTINGS)
- perms["accounts"] = has_permission(p, PERMS.ACCOUNTS)
+ """Returns a dict with permission key
+
+ :param perms: dictionary
+ :param p: bits
+ """
+ for name in permlist():
+ perms[name] = has_permission(p, getattr(PERMS, name))
+
def set_permission(perms):
+ """generates permission bits from dictionary
+
+ :param perms: dict
+ """
permission = 0
- if perms["add"]:
- permission |= PERMS.ADD
- if perms["delete"]:
- permission |= PERMS.DELETE
- if perms["status"]:
- permission |= PERMS.STATUS
- if perms["see_downloads"]:
- permission |= PERMS.SEE_DOWNLOADS
- if perms["download"]:
- permission |= PERMS.DOWNLOAD
- if perms["settings"]:
- permission |= PERMS.SETTINGS
- if perms["accounts"]:
- permission |= PERMS.ACCOUNTS
+ for name in dir(PERMS):
+ if name.startswith("_"): continue
+
+ if name in perms and perms[name]:
+ permission |= getattr(PERMS, name)
return permission
@@ -94,11 +90,13 @@ def set_session(request, info):
return s
+
def parse_userdata(session):
return {"name": session.get("name", "Anonymous"),
"is_admin": True if session.get("role", 1) == 0 else False,
"is_authenticated": session.get("authenticated", False)}
+
def login_required(perm=None):
def _dec(func):
def _view(*args, **kwargs):
@@ -123,14 +121,15 @@ def login_required(perm=None):
return _dec
+
def toDict(obj):
ret = {}
for att in obj.__slots__:
ret[att] = getattr(obj, att)
return ret
-class CherryPyWSGI(ServerAdapter):
+class CherryPyWSGI(ServerAdapter):
def run(self, handler):
from wsgiserver import CherryPyWSGIServer
diff --git a/module/web/webinterface.py b/module/web/webinterface.py
index f6ad35138..8f814715f 100644
--- a/module/web/webinterface.py
+++ b/module/web/webinterface.py
@@ -89,6 +89,7 @@ env.filters["path_make_absolute"] = path_make_absolute
env.filters["decode"] = decode
env.filters["type"] = lambda x: str(type(x))
env.filters["formatsize"] = formatSize
+env.filters["getitem"] = lambda x,y: x.__getitem__(y)
if PREFIX:
env.filters["url"] = lambda x: x
else:
diff --git a/pyLoadCore.py b/pyLoadCore.py
index bb1bd28f3..b95d6f072 100755
--- a/pyLoadCore.py
+++ b/pyLoadCore.py
@@ -66,6 +66,8 @@ else:
sys.stdout = getwriter(enc)(sys.stdout, errors="replace")
# TODO List
+# - configurable auth system ldap/mysql
+# - cron job like sheduler
class Core(object):
"""pyLoad Core, one tool to rule them all... (the filehosters) :D"""