diff options
author | RaNaN <Mast3rRaNaN@hotmail.de> | 2012-08-13 17:40:10 +0200 |
---|---|---|
committer | RaNaN <Mast3rRaNaN@hotmail.de> | 2012-08-13 17:40:10 +0200 |
commit | 941e3021000e59020f66419cc2156aee30972121 (patch) | |
tree | 49332fb148dd50c0ee78e4c20336c2848921bc1a | |
parent | merge (diff) | |
download | pyload-941e3021000e59020f66419cc2156aee30972121.tar.xz |
working login
-rw-r--r-- | module/Api.py | 63 | ||||
-rw-r--r-- | module/database/UserDatabase.py | 15 | ||||
-rw-r--r-- | module/datatypes/User.py | 40 | ||||
-rw-r--r-- | module/datatypes/__init__.py | 1 | ||||
-rw-r--r-- | module/remote/thriftbackend/Processor.py | 1 | ||||
-rw-r--r-- | module/remote/thriftbackend/pyload.thrift | 15 | ||||
-rw-r--r-- | module/setup.py | 6 | ||||
-rw-r--r-- | module/web/api_app.py | 16 | ||||
-rw-r--r-- | module/web/pyload_app.py | 50 | ||||
-rw-r--r-- | module/web/static/css/default/style.css | 35 | ||||
-rw-r--r-- | module/web/templates/default/base.html | 12 | ||||
-rw-r--r-- | module/web/templates/default/login.html | 5 | ||||
-rw-r--r-- | module/web/templates/default/logout.html | 9 | ||||
-rw-r--r-- | module/web/utils.py | 83 | ||||
-rwxr-xr-x | pyLoadCore.py | 1 |
15 files changed, 163 insertions, 189 deletions
diff --git a/module/Api.py b/module/Api.py index 388dfd283..26a6c757f 100644 --- a/module/Api.py +++ b/module/Api.py @@ -37,7 +37,7 @@ if activated: else: from remote.socketbackend.ttypes import * -from datatypes import PyFile +from datatypes.PyFile import PyFile from utils import compare_time, to_string, bits_set, get_index from utils.fs import free_space from common.packagetools import parseNames @@ -103,21 +103,26 @@ urlmatcher = re.compile(r"((https?|ftps?|xdcc|sftp):((//)|(\\\\))+[\w\d:#@%/;$() def has_permission(userPermission, Permission): return bits_set(Permission, userPermission) +from datatypes.User import User class UserApi(object): """ Proxy object for api that provides all methods in user context """ def __init__(self, api, user): self.api = api - self.user = user + self._user = user def __getattr__(self, item): f = self.api.__getattribute__(item) if f.func_name in user_context: - return partial(f, user=self.user) + return partial(f, user=self._user) return f + @property + def user(self): + return self._user + # TODO: fix permissions, user context manager class Api(Iface): @@ -138,31 +143,31 @@ class Api(Iface): def __init__(self, core): self.core = core + self.user_apis = {} - self.t = self.withUserContext("TestUser") - - print self.t.getServerVersion() - - # TODO, create user instance - def withUserContext(self, user): + def withUserContext(self, uid): """ Returns a proxy version of the api, to call method in user context - :param user: user id + :param uid: user or userData instance or uid :return: :class:`UserApi` """ - return UserApi(self, user) + if uid not in self.user_apis: + user = self.core.db.getUserData(uid=uid) + if not user: #TODO: anonymous user? + return None + + self.user_apis[uid] = UserApi(self, User.fromUserData(self, user)) + return self.user_apis[uid] ########################## # Server Status ########################## - @UserContext #TODO: only for testing @RequirePerm(Permission.All) def getServerVersion(self): """pyLoad Core version """ - print user return self.core.version @RequirePerm(Permission.Status) @@ -873,7 +878,7 @@ class Api(Iface): @RequirePerm(Permission.Status) def getEvents(self, uuid): - """Lists occured events, may be affected to changes in future. + """Lists occurred events, may be affected to changes in future. :param uuid: self assigned string uuid which has to be unique :return: list of `Events` @@ -925,6 +930,8 @@ class Api(Iface): # Auth+User Information ############################# + # TODO + @RequirePerm(Permission.All) def login(self, username, password, remoteip=None): """Login into pyLoad, this **must** be called when using rpc before any methods can be used. @@ -946,48 +953,42 @@ class Api(Iface): """ 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" self.core.log.info(_("User '%s' tried to log in") % username) return self.core.db.checkAuth(username, password) - def isAuthorized(self, func, userdata): + def isAuthorized(self, func, user): """checks if the user is authorized for specific method :param func: function name - :param userdata: dictionary of user data + :param user: `User` :return: boolean """ - if userdata == "local" or userdata["role"] == ROLE.ADMIN: + if user.isAdmin(): return True - elif func in perm_map and has_permission(userdata["permission"], perm_map[func]): + elif func in perm_map and user.hasPermission(perm_map[func]): return True else: return False - + # TODO @RequirePerm(Permission.All) def getUserData(self, username, password): """similar to `checkAuth` but returns UserData thrift type """ user = self.checkAuth(username, password) - if user: - return UserData(user["name"], user["email"], user["role"], user["permission"], user["template"]) + if not user: + raise UserDoesNotExists(username) - raise UserDoesNotExists(username) + return user.toUserData() def getAllUserData(self): """returns all known user and info""" - res = {} - for user, data in self.core.db.getAllUserData().iteritems(): - res[user] = UserData(user, data["email"], data["role"], data["permission"], data["template"]) - - return res + return self.core.db.getAllUserData() - def changePassword(self, user, oldpw, newpw): + def changePassword(self, username, oldpw, newpw): """ changes password for specific user """ - return self.core.db.changePassword(user, oldpw, newpw) + return self.core.db.changePassword(username, oldpw, newpw) def setUserPermission(self, user, permission, role): self.core.db.setPermission(user, permission) diff --git a/module/database/UserDatabase.py b/module/database/UserDatabase.py index bed4e94a9..0df94e0eb 100644 --- a/module/database/UserDatabase.py +++ b/module/database/UserDatabase.py @@ -16,17 +16,23 @@ ############################################################################### from hashlib import sha1 -import random +from string import letters, digits +from random import choice + +alphnum = letters+digits from module.Api import UserData from DatabaseBackend import DatabaseMethods, queue, async +def random_salt(): + return "".join(choice(alphnum) for x in range(0,5)) + class UserMethods(DatabaseMethods): @queue def addUser(self, user, password): - salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for i in range(0, 5)]) + salt = random_salt() h = sha1(salt + password) password = salt + h.hexdigest() @@ -69,11 +75,10 @@ class UserMethods(DatabaseMethods): @queue def checkAuth(self, user, password): self.c.execute('SELECT uid, name, email, role, permission, folder, traffic, dllimit, dlquota, ' - 'hddquota, user, template password FROM "users" WHERE name=?', (user, )) + 'hddquota, user, template, password FROM "users" WHERE name=?', (user, )) r = self.c.fetchone() if not r: return None - salt = r[-1][:5] pw = r[-1][5:] h = sha1(salt + password) @@ -93,7 +98,7 @@ class UserMethods(DatabaseMethods): pw = r[2][5:] h = sha1(salt + oldpw) if h.hexdigest() == pw: - salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for i in range(0, 5)]) + salt = random_salt() h = sha1(salt + newpw) password = salt + h.hexdigest() diff --git a/module/datatypes/User.py b/module/datatypes/User.py index 6ea958770..d48111182 100644 --- a/module/datatypes/User.py +++ b/module/datatypes/User.py @@ -17,17 +17,45 @@ ############################################################################### -from module.Api import UserData +from module.Api import UserData, Permission, Role, has_permission +#TODO: activate user #noinspection PyUnresolvedReferences class User(UserData): @staticmethod - def fromUserData(manager, user): - return User(manager, user.uid, user.name, user.email, user.role, user.permission, user.folder, + def fromUserData(api, user): + return User(api, user.uid, user.name, user.email, user.role, user.permission, user.folder, user.traffic, user.dllimit, user.dlquota, user.hddquota, user.user, user.templateName) - def __init__(self, manager, *args): - UserData.__init__(*args) - self.m = manager + def __init__(self, api, *args): + UserData.__init__(self, *args) + self.api = api + + def toUserData(self): + return UserData() + + def hasPermission(self, perms): + """ Accepts permission bit or name """ + + if isinstance(perms, basestring) and hasattr(Permission, perms): + perms = getattr(Role, perms) + + return has_permission(self.permission, perms) + + def hasRole(self, role): + if isinstance(role, basestring) and hasattr(Role, role): + role = getattr(Role, role) + + return self.role == role + + def isAdmin(self): + return self.hasRole(Role.Admin) + + @property + def handle(self): + """ Internal user handle used for most operations (secondary share handle with primary user) """ + if self.hasRole(Role.Admin): + return None + return self.user if self.user else self.uid diff --git a/module/datatypes/__init__.py b/module/datatypes/__init__.py index 4b31e848b..e69de29bb 100644 --- a/module/datatypes/__init__.py +++ b/module/datatypes/__init__.py @@ -1 +0,0 @@ -__author__ = 'christian' diff --git a/module/remote/thriftbackend/Processor.py b/module/remote/thriftbackend/Processor.py index a8b87c82c..6f822e98f 100644 --- a/module/remote/thriftbackend/Processor.py +++ b/module/remote/thriftbackend/Processor.py @@ -2,6 +2,7 @@ from thriftgen.pyload import Pyload +#TODO: new login class Processor(Pyload.Processor): def __init__(self, *args, **kwargs): Pyload.Processor.__init__(self, *args, **kwargs) diff --git a/module/remote/thriftbackend/pyload.thrift b/module/remote/thriftbackend/pyload.thrift index e0fa9c040..c628dff78 100644 --- a/module/remote/thriftbackend/pyload.thrift +++ b/module/remote/thriftbackend/pyload.thrift @@ -51,20 +51,22 @@ enum FileStatus { enum PackageStatus { Ok, Paused, + Folder, Remote, } // types for user interaction // some may only be place holder currently not supported // also all input - output combination are not reasonable, see InteractionManager for further info +// Todo: how about: time, int, ip, file, s.o. enum Input { NA, Text, - TextBox, + Textbox, Password, Bool, // confirm like, yes or no dialog Click, // for positional captchas - Choice, // choice from list + Select, // select from list Multiple, // multiple choice from list of elements List, // arbitary list of elements Table // table like data structure @@ -85,11 +87,10 @@ enum Permission { Add = 1, // can add packages Delete = 2, // can delete packages Modify = 4, // modify some attribute of downloads - Status = 8, // see and change server status - Download = 16, // can download from webinterface - Accounts = 32, // can access accounts - Interaction = 64, // can interact with plugins - Addons = 128 // user can activate addons + Download = 8, // can download from webinterface + Accounts = 16, // can access accounts + Interaction = 32, // can interact with plugins + Addons = 64 // user can activate addons } enum Role { diff --git a/module/setup.py b/module/setup.py index ed3829e40..de93089b3 100644 --- a/module/setup.py +++ b/module/setup.py @@ -365,10 +365,10 @@ class Setup(): print "" print _("Users") print "-----" - users = db.listUsers() + users = db.getAllUserData() noaction = False - for user in users: - print user + for user in users.itervalues(): + print user.name print "-----" print "" elif action == "3": diff --git a/module/web/api_app.py b/module/web/api_app.py index df62db18f..4be6e5ab8 100644 --- a/module/web/api_app.py +++ b/module/web/api_app.py @@ -7,7 +7,7 @@ from traceback import format_exc, print_exc from bottle import route, request, response, HTTPError -from utils import set_session +from utils import set_session, get_user_api from webinterface import PYLOAD from module.common.json_layer import json @@ -41,10 +41,11 @@ def call_api(func, args=""): # removes "' so it works on json strings s = s.get_by_id(remove_chars(request.POST['session'], "'\"")) - if not s or not s.get("authenticated", False): + api = get_user_api(s) + if not api: return HTTPError(403, json.dumps("Forbidden")) - if not PYLOAD.isAuthorized(func, {"role": s["role"], "permission": s["perms"]}): + if not PYLOAD.isAuthorized(func, api.user): return HTTPError(401, json.dumps("Unauthorized")) args = args.split("/")[1:] @@ -81,21 +82,22 @@ def callApi(func, *args, **kwargs): def login(): add_header(response) - user = request.forms.get("username") + username = request.forms.get("username") password = request.forms.get("password") - info = PYLOAD.checkAuth(user, password) + user = PYLOAD.checkAuth(username, password) - if not info: + if not user: return json.dumps(False) - s = set_session(request, info) + s = set_session(request, user) # get the session id by dirty way, documentations seems wrong try: sid = s._headers["cookie_out"].split("=")[1].split(";")[0] return json.dumps(sid) except: + print "Could not get session" return json.dumps(True) diff --git a/module/web/pyload_app.py b/module/web/pyload_app.py index ba74d7083..afb97b361 100644 --- a/module/web/pyload_app.py +++ b/module/web/pyload_app.py @@ -23,43 +23,27 @@ from bottle import route, static_file, request, response, redirect, HTTPError, e from webinterface import PYLOAD, PROJECT_DIR, SETUP, env -from utils import render_to_response, parse_permissions, parse_userdata, set_session +from utils import render_to_response, login_required, set_session, get_user_api -from module.Api import Output ########## # Helper ########## - # TODO: useful but needs a rewrite, too def pre_processor(): s = request.environ.get('beaker.session') - user = parse_userdata(s) - perms = parse_permissions(s) - status = {} - captcha = False - update = False - plugins = False - if user["is_authenticated"]: - status = PYLOAD.statusServer() - info = PYLOAD.getInfoByPlugin("UpdateManager") - captcha = PYLOAD.isInteractionWaiting(Output.Captcha) - - # check if update check is available - if info: - if info["pyload"] == "True": update = True - if info["plugins"] == "True": plugins = True + api = get_user_api(s) + user = None + status = None + if api is not None: + user = api.user + status = api.statusServer() return {"user": user, - 'status': status, - 'captcha': captcha, - 'perms': perms, - 'url': request.url, - 'update': update, - 'plugins': plugins} - + 'server': status, + 'url': request.url } def base(messages): @@ -68,11 +52,11 @@ def base(messages): @error(500) def error500(error): - print "An error occured while processing the request." + print "An error occurred while processing the request." if error.traceback: print error.traceback - return base(["An error occured while processing the request.", error, + return base(["An error occurred while processing the request.", error, error.traceback.replace("\n", "<br>") if error.traceback else "No Traceback"]) # TODO: not working @@ -125,15 +109,14 @@ def nopermission(): @route("/login", method="POST") def login_post(): - user = request.forms.get("username") + username = request.forms.get("username") password = request.forms.get("password") - info = PYLOAD.checkAuth(user, password) - - if not info: + user = PYLOAD.checkAuth(username, password) + if not user: return render_to_response("login.html", {"errors": True}, [pre_processor]) - set_session(request, info) + set_session(request, user) return redirect("/") @@ -144,6 +127,7 @@ def logout(): return render_to_response("logout.html", proc=[pre_processor]) @route("/") -def index(): +@login_required() +def index(api): return base(["It works!"]) diff --git a/module/web/static/css/default/style.css b/module/web/static/css/default/style.css index 326111680..63a7ad959 100644 --- a/module/web/static/css/default/style.css +++ b/module/web/static/css/default/style.css @@ -151,27 +151,24 @@ header .logo { margin-top: 12px;
font-family: sans-serif
}
- +
/*
Login
-*/ -.login { - vertical-align: middle; - text-align: center; - border: 1px; - border-color:#000000; - border-width:2px; - border-style:solid; - padding: 15px; - -moz-border-radius: 15px; - border-radius: 15px; -} - -.login div{ - vertical-align: middle; - text-align: center; - padding: 3px; -} +*/
+.login {
+ vertical-align: middle;
+ text-align: center;
+ border: 2px solid #000000;
+ padding: 15px;
+ -moz-border-radius: 15px;
+ border-radius: 15px;
+}
+
+.login div{
+ vertical-align: middle;
+ text-align: center;
+ padding: 3px;
+}
/*
Footer
diff --git a/module/web/templates/default/base.html b/module/web/templates/default/base.html index bbbefb4b6..e1fe81f1b 100644 --- a/module/web/templates/default/base.html +++ b/module/web/templates/default/base.html @@ -27,16 +27,17 @@ <div class="logo"></div>
<span class="title">pyLoad</span>
+ {% if user %}
<div id="notification_div">
<h1>Important Stuff will be here!</h1>
</div>
<div class="header_block">
<div class="icon_info">
- <img src="static/img/default/icon_user_small_white.png" height="20px"/><span>User</span>
+ <img src="static/img/default/icon_user_small_white.png" height="20px"/><span>{{ user.name }}</span>
</div>
<div class="icon_info" style="text-align: center">
- Logout
+ <a href="logout">Logout</a>
</div>
</div>
<div id="speedgraph"></div>
@@ -48,16 +49,17 @@ <img src="static/img/default/icon_clock_small_white.png" height="20px"/><span>5 / 125</span>
</div>
</div>
+ {% endif %}
</div>
</header>
<div id="push"></div>
<div id="content">
{% for message in messages %}
- <b><p>{{ message }}</p></b>
+ <div style="text-align: center">
+ <b>{{ message }}</b><br/>
+ </div>
{% endfor %}
- <h1>Test!</h1>
-
{% block content %}
{% endblock content %}
</div>
diff --git a/module/web/templates/default/login.html b/module/web/templates/default/login.html index 914ec6f16..95c62a992 100644 --- a/module/web/templates/default/login.html +++ b/module/web/templates/default/login.html @@ -3,7 +3,7 @@ {% block title %}{{_("Login")}} - {{super()}} {% endblock %} {% block content %} - +<br> <div class="login"> <div class="login_title"> {{_("Login")}} @@ -24,11 +24,12 @@ </form> </div> - +<div style="text-align: center"> {% if errors %} <p>{{_("Your username and password didn't match. Please try again.")}}</p> {{ _("To reset your login data or add an user run:") }} <b> python pyLoadCore.py -u</b> {% endif %} +</div> {% endblock %}
\ No newline at end of file diff --git a/module/web/templates/default/logout.html b/module/web/templates/default/logout.html new file mode 100644 index 000000000..a100c7004 --- /dev/null +++ b/module/web/templates/default/logout.html @@ -0,0 +1,9 @@ +{% extends 'default/base.html' %} + +{% block head %} + <meta http-equiv="refresh" content="3; url=/"> +{% endblock %} + +{% block content %} + <p><b>{{_("fYou were successfully logged out.")}}</b></p> +{% endblock %} diff --git a/module/web/utils.py b/module/web/utils.py index 364f12bf4..43847b6c8 100644 --- a/module/web/utils.py +++ b/module/web/utils.py @@ -12,15 +12,13 @@ See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this plrogram; if not, see <http://www.gnu.org/licenses/>. + along with this program; if not, see <http://www.gnu.org/licenses/>. @author: RaNaN """ from bottle import request, HTTPError, redirect, ServerAdapter -from webinterface import env, TEMPLATE - -from module.Api import has_permission, Permission, Role +from webinterface import env, TEMPLATE, PYLOAD def render_to_response(name, args={}, proc=[]): for p in proc: @@ -29,87 +27,34 @@ def render_to_response(name, args={}, proc=[]): return t.render(**args) -def parse_permissions(session): - perms = dict([(x, False) for x in dir(Permission) if not x.startswith("_")]) - perms["ADMIN"] = False - perms["is_admin"] = False - - if not session.get("authenticated", False): - return perms - - if session.get("role") == Role.Admin: - for k in perms.iterkeys(): - perms[k] = True - - elif session.get("perms"): - p = session.get("perms") - get_permission(perms, p) - - return perms - - -def permlist(): - return [x for x in dir(Permission) if not x.startswith("_") and x != "All"] - - -def get_permission(perms, p): - """Returns a dict with permission key - - :param perms: dictionary - :param p: bits - """ - for name in permlist(): - perms[name] = has_permission(p, getattr(Permission, name)) - - -def set_permission(perms): - """generates permission bits from dictionary - - :param perms: dict - """ - permission = 0 - for name in dir(Permission): - if name.startswith("_"): continue - - if name in perms and perms[name]: - permission |= getattr(Permission, name) - - return permission - - -def set_session(request, info): +def set_session(request, user): s = request.environ.get('beaker.session') - s["authenticated"] = True - s["user_id"] = info["id"] - s["name"] = info["name"] - s["role"] = info["role"] - s["perms"] = info["permission"] - s["template"] = info["template"] + s["uid"] = user.uid s.save() - return s +def get_user_api(s): + uid = s.get("uid", None) + if uid is not None: + api = PYLOAD.withUserContext(uid) + return api -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)} - + return None def login_required(perm=None): def _dec(func): def _view(*args, **kwargs): s = request.environ.get('beaker.session') - if s.get("name", None) and s.get("authenticated", False): + api = get_user_api(s) + if api is not None: if perm: - perms = parse_permissions(s) - - if perm not in perms or not perms[perm]: + if api.user.hasPermission(perm): if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return HTTPError(403, "Forbidden") else: return redirect("/nopermission") + kwargs["api"] = api return func(*args, **kwargs) else: if request.headers.get('X-Requested-With') == 'XMLHttpRequest': diff --git a/pyLoadCore.py b/pyLoadCore.py index e3a90575b..99c01dbf7 100755 --- a/pyLoadCore.py +++ b/pyLoadCore.py @@ -87,7 +87,6 @@ class Core(object): def __init__(self): self.doDebug = False - self.startedInGui = False self.running = False self.daemon = False self.remote = True |