summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2012-08-13 17:40:10 +0200
committerGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2012-08-13 17:40:10 +0200
commit941e3021000e59020f66419cc2156aee30972121 (patch)
tree49332fb148dd50c0ee78e4c20336c2848921bc1a
parentmerge (diff)
downloadpyload-941e3021000e59020f66419cc2156aee30972121.tar.xz
working login
-rw-r--r--module/Api.py63
-rw-r--r--module/database/UserDatabase.py15
-rw-r--r--module/datatypes/User.py40
-rw-r--r--module/datatypes/__init__.py1
-rw-r--r--module/remote/thriftbackend/Processor.py1
-rw-r--r--module/remote/thriftbackend/pyload.thrift15
-rw-r--r--module/setup.py6
-rw-r--r--module/web/api_app.py16
-rw-r--r--module/web/pyload_app.py50
-rw-r--r--module/web/static/css/default/style.css35
-rw-r--r--module/web/templates/default/base.html12
-rw-r--r--module/web/templates/default/login.html5
-rw-r--r--module/web/templates/default/logout.html9
-rw-r--r--module/web/utils.py83
-rwxr-xr-xpyLoadCore.py1
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