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
-~----------~----~----~----~------~----~------~--~---

Reply via email to