Author: damoxc
Revision: 5648
Log:
complete the new auth system using cookies & sessions
Diff:
Modified: trunk/deluge/ui/web/auth.py
===================================================================
--- trunk/deluge/ui/web/auth.py 2009-08-11 23:15:42 UTC (rev 5647)
+++ trunk/deluge/ui/web/auth.py 2009-08-11 23:39:58 UTC (rev 5648)
@@ -40,18 +40,47 @@
AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL
+class AuthError(Exception):
+ """
+ An exception that might be raised when checking a request for
+ authentication.
+ """
+ pass
+
import time
import random
import hashlib
import logging
from twisted.internet.defer import Deferred
+from twisted.internet.task import LoopingCall
from deluge import component
from deluge.ui.web.json_api import JSONComponent, export
log = logging.getLogger(__name__)
+def make_checksum(session_id):
+ return reduce(lambda x,y:x+y, map(ord, session_id))
+
+def get_session_id(session_id):
+ """
+ Checks a session id against its checksum
+ """
+ if not session_id:
+ return None
+
+ try:
+ checksum = int(session_id[-4:])
+ session_id = session_id[:-4]
+
+ if checksum == make_checksum(session_id):
+ return session_id
+ return None
+ except Exception, e:
+ log.exception(e)
+ return None
+
class Auth(JSONComponent):
"""
The component that implements authentification into the JSON interface.
@@ -59,13 +88,31 @@
def __init__(self):
super(Auth, self).__init__("Auth")
+ self.worker = LoopingCall(self._clean_sessions)
+ self.worker.start(5)
- def _create_session(self, login='admin'):
+ def _clean_sessions(self):
+ config = component.get("DelugeWeb").config
+ session_ids = config["sessions"].keys()
+
+ now = time.gmtime()
+ for session_id in session_ids:
+ session = config["sessions"][session_id]
+
+ if "expires" not in session:
+ del config["sessions"][session_id]
+ continue
+
+ if time.gmtime(session["expires"]) < now:
+ del config["sessions"][session_id]
+ continue
+
+ def _create_session(self, request, login='admin'):
"""
Creates a new session.
:keyword login: the username of the user logging in, currently \
- only for future use.
+ only for future use currently.
:type login: string
"""
m = hashlib.md5()
@@ -75,6 +122,16 @@
m.update(m.hexdigest())
session_id = m.hexdigest()
+ expires = int(time.time()) + 3600
+ expires_str = time.strftime('%a, %d %b %Y %H:%M:%S UTC',
+ time.gmtime(expires))
+
+ checksum = str(make_checksum(session_id))
+
+ print 'Adding cookie'
+ request.addCookie('_session_id', session_id + checksum,
+ path="/json", expires=expires_str)
+
log.debug("Creating session for %s", login)
config = component.get("DelugeWeb").config
@@ -82,10 +139,55 @@
config.config["sessions"] = {}
config["sessions"][session_id] = {
- "login": login
+ "login": login,
+ "level": AUTH_LEVEL_ADMIN,
+ "expires": expires
}
- return session_id
+ return True
+ def check_request(self, request, method=None, level=None):
+ """
+ Check to ensure that a request is authorised to call the specified
+ method of authentication level.
+
+ :param request: The HTTP request in question
+ :type request: twisted.web.http.Request
+ :keyword method: Check the specified method
+ :type method: function
+ :keyword level: Check the specified auth level
+ :type level: integer
+
+ :raises: Exception
+ """
+
+ config = component.get("DelugeWeb").config
+ session_id = get_session_id(request.getCookie("_session_id"))
+
+ if session_id not in config["sessions"]:
+ auth_level = AUTH_LEVEL_NONE
+ session_id = None
+ else:
+ auth_level = config["sessions"][session_id]["level"]
+
+ if method:
+ if not hasattr(method, "_json_export"):
+ raise Exception("Not an exported method")
+
+ method_level = getattr(method, "_json_auth_level")
+ if method_level is None:
+ raise Exception("Method has no auth level")
+
+ level = method_level
+
+ if level is None:
+ raise Exception("No level specified to check against")
+
+ request.auth_level = auth_level
+ request.session_id = session_id
+
+ if auth_level < level:
+ raise AuthError("Not authenticated")
+
@export
def change_password(self, new_password):
"""
@@ -104,25 +206,21 @@
config["pwd_sha1"] = s.hexdigest()
d.callback(True)
return d
-
- @export
- def check_session(self, session_id):
+ @export(AUTH_LEVEL_NONE)
+ def check_session(self, session_id=None):
"""
Check a session to see if it's still valid.
- :param session_id: the id for the session to remove
- :type session_id: string
:returns: True if the session is valid, False if not.
:rtype: booleon
"""
d = Deferred()
- config = component.get("DelugeWeb").config
- d.callback(session_id in config["sessions"])
+ d.callback(__request__.session_id is not None)
return d
@export
- def delete_session(self, session_id):
+ def delete_session(self):
"""
Removes a session.
@@ -131,11 +229,11 @@
"""
d = Deferred()
config = component.get("DelugeWeb").config
- del config["sessions"][session_id]
+ del config["sessions"][__request__.session_id]
d.callback(True)
return d
- @export
+ @export(AUTH_LEVEL_NONE)
def login(self, password):
"""
Test a password to see if it's valid.
@@ -177,7 +275,7 @@
m.update(password)
if m.digest() == decodestring(config["old_pwd_md5"]):
# We have a match, so we can create and return a session id.
- d.callback(self._create_session())
+ d.callback(self._create_session(__request__))
# We also want to move the password over to sha1 and remove
# the old passwords from the config file.
@@ -193,7 +291,7 @@
s.update(password)
if s.hexdigest() == config["pwd_sha1"]:
# We have a match, so we can create and return a session id.
- d.callback(self._create_session())
+ d.callback(self._create_session(__request__))
else:
# Can't detect which method we should be using so just deny
Modified: trunk/deluge/ui/web/js/Deluge.Client.js
===================================================================
--- trunk/deluge/ui/web/js/Deluge.Client.js 2009-08-11 23:15:42 UTC (rev
5647)
+++ trunk/deluge/ui/web/js/Deluge.Client.js 2009-08-11 23:39:58 UTC (rev
5648)
@@ -103,28 +103,34 @@
errorObj = {
id: options.id,
result: null,
- error: 'HTTP: ' + response.status + ' ' + response.statusText
- }
+ error: {
+ msg: 'HTTP: ' + response.status + ' ' +
response.statusText,
+ code: 255
+ }
+ }
+
+ this.fireEvent('error', errorObj, response, requestOptions)
+
if (Ext.type(options.failure) != 'function') return;
if (options.scope) {
options.failure.call(options.scope, errorObj, response,
requestOptions);
} else {
options.failure(errorObj, response, requestOptions);
- }
- this.fireEvent('error', errorObj, response, requestOptions)
+ }
},
_onSuccess: function(response, requestOptions) {
var responseObj = Ext.decode(response.responseText);
var options = requestOptions.options;
if (responseObj.error) {
+ this.fireEvent('error', responseObj, response, requestOptions);
+
if (Ext.type(options.failure) != 'function') return;
if (options.scope) {
options.failure.call(options.scope, responseObj, response,
requestOptions);
} else {
options.failure(responseObj, response, requestOptions);
}
- this.fireEvent('error', responseObj, response, requestOptions)
} else {
if (Ext.type(options.success) != 'function') return;
if (options.scope) {
Modified: trunk/deluge/ui/web/js/Deluge.Login.js
===================================================================
--- trunk/deluge/ui/web/js/Deluge.Login.js 2009-08-11 23:15:42 UTC (rev
5647)
+++ trunk/deluge/ui/web/js/Deluge.Login.js 2009-08-11 23:39:58 UTC (rev
5648)
@@ -57,9 +57,7 @@
initComponent: function() {
Ext.deluge.LoginWindow.superclass.initComponent.call(this);
- Deluge.Events.on('logout', this.onLogout, this);
this.on('show', this.onShow, this);
- this.on('beforeshow', this.onBeforeShow, this);
this.addButton({
text: _('Login'),
@@ -89,6 +87,33 @@
})
},
+ show: function(skipCheck) {
+ if (this.firstShow) {
+ Deluge.Client.on('error', this.onClientError,
this);
+ this.firstShow = false;
+ }
+
+ if (skipCheck) {
+ return
Ext.deluge.LoginWindow.superclass.show.call(this);
+ }
+
+ Deluge.Client.auth.check_session({
+ success: function(result) {
+ if (result) {
+ Deluge.Events.fire('login');
+
this.loginForm.items.get('password').setRawValue('');
+ this.hide();
+ } else {
+ this.show(true);
+ }
+ },
+ failure: function(result) {
+ this.show(true);
+ },
+ scope: this
+ });
+ },
+
onKey: function(field, e) {
if (e.getKey() == 13) this.onLogin();
},
@@ -101,7 +126,6 @@
Deluge.Events.fire('login');
this.hide();
passwordField.setRawValue('');
-
Deluge.UI.cookies.set("session", result);
} else {
Ext.MessageBox.show({
title: _('Login
Failed'),
@@ -121,39 +145,19 @@
},
onLogout: function() {
- var session = Deluge.UI.cookies.get("session", false);
- if (session) {
- Deluge.Client.auth.delete_session(session, {
- success: function(result) {
-
Deluge.UI.cookies.clear("session");
- this.show();
- },
- scope: this
- });
- }
+ Deluge.Events.fire('logout');
+ Deluge.Client.auth.delete_session({
+ success: function(result) {
+ this.show(true);
+ },
+ scope: this
+ });
},
- onBeforeShow: function() {
- var session = Deluge.UI.cookies.get("session", false);
- if (session) {
- Deluge.Client.auth.check_session(session, {
- success: function(result) {
- if (result) {
-
Deluge.Events.fire('login');
-
this.loginForm.items.get('password').setRawValue('');
- this.hide();
- } else {
-
Deluge.UI.cookies.clear("session");
- this.show();
- }
- },
- failure: function(result) {
-
Deluge.UI.cookies.clear("session");
- this.show();
- },
- scope: this
- });
- return false;
+ onClientError: function(errorObj, response, requestOptions) {
+ if (errorObj.error.code == 1) {
+ Deluge.Events.fire('logout');
+ this.show(true);
}
},
Modified: trunk/deluge/ui/web/js/Deluge.Toolbar.js
===================================================================
--- trunk/deluge/ui/web/js/Deluge.Toolbar.js 2009-08-11 23:15:42 UTC (rev
5647)
+++ trunk/deluge/ui/web/js/Deluge.Toolbar.js 2009-08-11 23:39:58 UTC (rev
5648)
@@ -150,8 +150,7 @@
onLogout: function() {
this.items.get('logout').disable();
- Deluge.Events.fire('logout');
- Deluge.Login.show();
+ Deluge.Login.logout();
},
onConnectionManagerClick: function() {
Modified: trunk/deluge/ui/web/json_api.py
===================================================================
--- trunk/deluge/ui/web/json_api.py 2009-08-11 23:15:42 UTC (rev 5647)
+++ trunk/deluge/ui/web/json_api.py 2009-08-11 23:39:58 UTC (rev 5648)
@@ -56,6 +56,7 @@
log = logging.getLogger(__name__)
AUTH_LEVEL_DEFAULT = None
+AuthError = None
class JSONComponent(component.Component):
def __init__(self, name, interval=1, depend=None):
@@ -74,9 +75,9 @@
:type auth_level: int
"""
- global AUTH_LEVEL_DEFAULT
+ global AUTH_LEVEL_DEFAULT, AuthError
if AUTH_LEVEL_DEFAULT is None:
- from deluge.ui.web.auth import AUTH_LEVEL_DEFAULT
+ from deluge.ui.web.auth import AUTH_LEVEL_DEFAULT, AuthError
def wrap(func, *args, **kwargs):
func._json_export = True
@@ -153,6 +154,7 @@
# and any plugins.
meth = self._local_methods[method]
meth.func_globals['__request__'] = request
+ component.get("Auth").check_request(request, meth)
return meth(*params)
raise JSONException("Unknown system method")
@@ -160,6 +162,7 @@
"""
Executes methods using the Deluge client.
"""
+ component.get("Auth").check_request(request, level=AUTH_LEVEL_DEFAULT)
component, method = method.split(".")
return getattr(getattr(client, component), method)(*params)
@@ -169,7 +172,6 @@
the rpc object that should be contained, returning a deferred for all
procedure calls and the request id.
"""
- request_id = None
try:
request.json = json.loads(request.json)
except ValueError:
@@ -181,20 +183,25 @@
method, params = request.json["method"], request.json["params"]
request_id = request.json["id"]
+ result = None
+ error = None
try:
- if method.startswith("system."):
- return self._exec_local(method, params, request), request_id
- elif method in self._local_methods:
- return self._exec_local(method, params, request), request_id
+ if method.startswith("system.") or method in self._local_methods:
+ result = self._exec_local(method, params, request)
elif method in self._remote_methods:
- return self._exec_remote(method, params), request_id
+ result = self._exec_remote(method, params)
+ else:
+ error = {"message": "Unknown method", "code": 2}
+ except AuthError, e:
+ error = {"message": "Not authenticated", "code": 1}
except Exception, e:
log.error("Error calling method `%s`", method)
log.exception(e)
- d = Deferred()
- d.callback(None)
- return d, request_id
+
+ error = {"message": e.message, "code": 3}
+
+ return request_id, result, error
def _on_rpc_request_finished(self, result, response, request):
"""
@@ -207,7 +214,6 @@
"""
Handles any failures that occured while making an rpc call.
"""
- print type(reason)
request.setResponseCode(http.INTERNAL_SERVER_ERROR)
return ""
@@ -218,11 +224,15 @@
"""
log.debug("json-request: %s", request.json)
response = {"result": None, "error": None, "id": None}
- d, response["id"] = self._handle_request(request)
- d.addCallback(self._on_rpc_request_finished, response, request)
- d.addErrback(self._on_rpc_request_failed, response, request)
- return d
+ response["id"], d, response["error"] = self._handle_request(request)
+ if isinstance(d, Deferred):
+ d.addCallback(self._on_rpc_request_finished, response, request)
+ d.addErrback(self._on_rpc_request_failed, response, request)
+ return d
+ else:
+ return self._send_response(request, response)
+
def _on_json_request_failed(self, reason, request):
"""
Errback handler to return a HTTP code of 500.
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"deluge-commit" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/deluge-commit?hl=en
-~----------~----~----~----~------~----~------~--~---