changeset 446:c406e46fe9c0

server_side: update the connection mechanism to handle profile passwords
author souliane <souliane@mailoo.org>
date Wed, 07 May 2014 19:46:58 +0200
parents c0ff91cabea0
children f94c3caea2ba
files libervia_server/__init__.py libervia_server/blog.py twisted/plugins/libervia.py
diffstat 3 files changed, 112 insertions(+), 93 deletions(-) [+]
line wrap: on
line diff
--- a/libervia_server/__init__.py	Wed May 07 19:29:10 2014 +0200
+++ b/libervia_server/__init__.py	Wed May 07 19:46:58 2014 +0200
@@ -566,8 +566,8 @@
            - except login, every method is jsonrpc
            - user doesn't need to be authentified for explicitely listed methods, but must be for all others
         """
-        if request.postpath==['login']:
-            return self.login(request)
+        if request.postpath == ['login']:
+            return self.loginOrRegister(request)
         _session = request.getSession()
         parsed = jsonrpclib.loads(request.content.read())
         method = parsed.get("method")
@@ -581,75 +581,78 @@
         self.request = request
         return jsonrpc.JSONRPC.render(self, request)
 
-    def login(self, request):
-        """
-        this method is called with the POST information from the registering form
-        it test if the password is ok, and log in if it's the case,
-        else it return an error
-        @param request: request of the register formulaire, must have "login" and "password" as arguments
-        @return: A constant indicating the state:
-            - BAD REQUEST: something is wrong in the request (bad arguments, profile_key for login)
-            - AUTH ERROR: either the profile or the password is wrong
-            - ALREADY WAITING: a request has already be made for this profile
-            - server.NOT_DONE_YET: the profile is being processed, the return value will be given by self._logged or self._logginError
-        """
+    def loginOrRegister(self, request):
+        """This method is called with the POST information from the registering form.
 
+        @param request: request of the register form
+        @return: a constant indicating the state:
+            - BAD REQUEST: something is wrong in the request (bad arguments)
+            - a return value from self._loginAccount or self._registerNewAccount
+        """
         try:
-            if request.args['submit_type'][0] == 'login':
-                _login = request.args['login'][0]
-                if _login.startswith('@'):
-                    raise Exception('No profile_key allowed')
-                _pass = request.args['login_password'][0]
-
-            elif request.args['submit_type'][0] == 'register':
-                return self._registerNewAccount(request)
-
-            else:
-                raise Exception('Unknown submit type')
+            submit_type = request.args['submit_type'][0]
         except KeyError:
             return "BAD REQUEST"
 
-        _profile_check = self.sat_host.bridge.getProfileName(_login)
-
-        def profile_pass_cb(_profile_pass):
-            if not _profile_check or _profile_check != _login or _profile_pass != _pass:
-                request.write("AUTH ERROR")
-                request.finish()
-                return
+        if submit_type == 'register':
+            return self._registerNewAccount(request)
+        elif submit_type == 'login':
+            return self._loginAccount(request)
+        return Exception('Unknown submit type')
 
-            if self.profiles_waiting.has_key(_login):
-                request.write("ALREADY WAITING")
-                request.finish()
-                return
+    def _loginAccount(self, request):
+        """Try to authenticate the user with the request information.
+        @param request: request of the register form
+        @return: a constant indicating the state:
+            - BAD REQUEST: something is wrong in the request (bad arguments)
+            - AUTH ERROR: either the profile (login) or the password is wrong
+            - ALREADY WAITING: a request has already been submitted for this profile
+            - server.NOT_DONE_YET: the profile is being processed, the return
+                value will be given by self._logged or self._logginError
+        """
+        try:
+            login_ = request.args['login'][0]
+            password_ = request.args['login_password'][0]
+        except KeyError:
+            return "BAD REQUEST"
 
-            if self.sat_host.bridge.isConnected(_login):
-                request.write(self._logged(_login, request, finish=False))
-                request.finish()
-                return
+        if login_.startswith('@'):
+            raise Exception('No profile_key allowed')
 
-            self.profiles_waiting[_login] = request
-            d = self.asyncBridgeCall("asyncConnect", _login)
-            return d
+        profile_check = self.sat_host.bridge.getProfileName(login_)
+        if not profile_check or profile_check != login_ or not password_:
+            # profiles with empty passwords are restricted to local frontends
+            return "AUTH ERROR"
 
-        def profile_pass_errback(ignore):
-            log.error("INTERNAL ERROR: can't check profile password")
+        if login_ in self.profiles_waiting:
+            return "ALREADY WAITING"
+
+        def auth_eb(ignore=None):
+            self.__cleanWaiting(login_)
+            log.info("Profile %s doesn't exist or the submitted password is wrong" % login_)
             request.write("AUTH ERROR")
             request.finish()
 
-        d = self.asyncBridgeCall("asyncGetParamA", "Password", "Connection", profile_key=_login)
-        d.addCallbacks(profile_pass_cb, profile_pass_errback)
+        self.profiles_waiting[login_] = request
+        d = self.asyncBridgeCall("asyncConnect", login_, password_)
+        d.addCallbacks(lambda connected: self._logged(login_, request) if connected else None, auth_eb)
 
         return server.NOT_DONE_YET
 
     def _registerNewAccount(self, request):
         """Create a new account, or return error
-        @param request: initial login request
-        @return: "REGISTRATION" in case of success"""
-        #TODO: must be moved in SàT core
-
+        @param request: request of the register form
+        @return: a constant indicating the state:
+            - BAD REQUEST: something is wrong in the request (bad arguments)
+            - REGISTRATION: new account has been successfully registered
+            - ALREADY EXISTS: the given profile already exists
+            - INTERNAL or 'Unknown error (...)'
+            - server.NOT_DONE_YET: the profile is being processed, the return
+                value will be given later (one of those previously described)
+        """
         try:
             profile = login = request.args['register_login'][0]
-            password = request.args['register_password'][0] #FIXME: password is ignored so far
+            password = request.args['register_password'][0]
             email = request.args['email'][0]
         except KeyError:
             return "BAD REQUEST"
@@ -685,27 +688,28 @@
         except KeyError:
             pass
 
-    def _logged(self, profile, request, finish=True):
-        """Set everything when a user just logged
-        and return "LOGGED" to the requester"""
-        def result(answer):
-            if finish:
-                request.write(answer)
-                request.finish()
-            else:
-                return answer
+    def _logged(self, profile, request):
+        """Set everything when a user just logged in
 
+        @param profile
+        @param request
+        @return: a constant indicating the state:
+            - LOGGED
+            - SESSION_ACTIVE
+        """
         self.__cleanWaiting(profile)
         _session = request.getSession()
         sat_session = ISATSession(_session)
         if sat_session.profile:
-            log.error (('/!\\ Session has already a profile, this should NEVER happen !'))
-            return result('SESSION_ACTIVE')
+            log.error(('/!\\ Session has already a profile, this should NEVER happen!'))
+            request.write('SESSION_ACTIVE')
+            request.finish()
+            return
         sat_session.profile = profile
         self.sat_host.prof_connected.add(profile)
 
         def onExpire():
-            log.info ("Session expired (profile=%s)" % (profile,))
+            log.info("Session expired (profile=%s)" % (profile,))
             try:
                 #We purge the queue
                 del self.sat_host.signal_handler.queue[profile]
@@ -716,10 +720,13 @@
 
         _session.notifyOnExpire(onExpire)
 
-        return result('LOGGED')
+        request.write('LOGGED')
+        request.finish()
 
     def _logginError(self, login, request, error_type):
-        """Something went wrong during loggin, return an error"""
+        """Something went wrong during logging in
+        @return: error
+        """
         self.__cleanWaiting(login)
         return error_type
 
@@ -840,7 +847,7 @@
         return genericCb
 
     def connected(self, profile):
-        assert(self.register) #register must be plugged
+        assert(self.register)  # register must be plugged
         request = self.register.getWaitingRequest(profile)
         if request:
             self.register._logged(profile, request)
@@ -991,6 +998,7 @@
                       ['ssl_certificate', 'c', 'libervia.pem', 'PEM certificate with both private and public parts.', str],
                       ['redirect_to_https', 'r', 1, 'automatically redirect from HTTP to HTTPS.', int],
                       ['security_warning', 'w', 1, 'warn user that he is about to connect on HTTP.', int],
+                      ['passphrase', 'k', '', u"passphrase for the SàT profile named '%s'" % C.SERVICE_PROFILE, str],
                       ]
 
     def __init__(self, *args, **kwargs):
@@ -1009,6 +1017,7 @@
         self.ssl_certificate = kwargs['ssl_certificate']
         self.redirect_to_https = kwargs['redirect_to_https']
         self.security_warning = kwargs['security_warning']
+        self.passphrase = kwargs['passphrase']
         self._cleanup = []
         root = ProtectedFile(C.LIBERVIA_DIR)
         self.signal_handler = SignalHandler(self)
@@ -1069,32 +1078,45 @@
         self._cleanup.insert(0, (callback, args, kwargs))
 
     def startService(self):
+        """Connect the profile for Libervia and start the HTTP(S) server(s)"""
+        def eb(e):
+            log.error(_("Connection failed: %s") % e)
+            self.stop()
+
         def initOk(dummy):
-            if self.connection_type in ('https', 'both'):
-                if not ssl_available:
-                    raise(ImportError(_("Python module pyOpenSSL is not installed!")))
-                try:
-                    with open(os.path.expanduser(self.ssl_certificate)) as keyAndCert:
-                        try:
-                            cert = ssl.PrivateCertificate.loadPEM(keyAndCert.read())
-                        except OpenSSL.crypto.Error as e:
-                            log.error(_("The file '%s' must contain both private and public parts of the certificate") % self.ssl_certificate)
-                            raise e
-                except IOError as e:
-                    log.error(_("The file '%s' doesn't exist") % self.ssl_certificate)
-                    raise e
-                reactor.listenSSL(self.port_https, self.site, cert.options())
-            if self.connection_type in ('http', 'both'):
-                if self.connection_type == 'both' and self.redirect_to_https:
-                    reactor.listenTCP(self.port, server.Site(RedirectToHTTPS(self.port, self.port_https_ext)))
-                else:
-                    reactor.listenTCP(self.port, self.site)
+            if not self.bridge.isConnected(C.SERVICE_PROFILE):
+                self.bridge.asyncConnect(C.SERVICE_PROFILE, self.passphrase,
+                                         callback=self._startService, errback=eb)
+
         self.initialised.addCallback(initOk)
 
+    def _startService(self, dummy):
+        """Actually start the HTTP(S) server(s) after the profile for Libervia is connected"""
+        if self.connection_type in ('https', 'both'):
+            if not ssl_available:
+                raise(ImportError(_("Python module pyOpenSSL is not installed!")))
+            try:
+                with open(os.path.expanduser(self.ssl_certificate)) as keyAndCert:
+                    try:
+                        cert = ssl.PrivateCertificate.loadPEM(keyAndCert.read())
+                    except OpenSSL.crypto.Error as e:
+                        log.error(_("The file '%s' must contain both private and public parts of the certificate") % self.ssl_certificate)
+                        raise e
+            except IOError as e:
+                log.error(_("The file '%s' doesn't exist") % self.ssl_certificate)
+                raise e
+            reactor.listenSSL(self.port_https, self.site, cert.options())
+        if self.connection_type in ('http', 'both'):
+            if self.connection_type == 'both' and self.redirect_to_https:
+                reactor.listenTCP(self.port, server.Site(RedirectToHTTPS(self.port, self.port_https_ext)))
+            else:
+                reactor.listenTCP(self.port, self.site)
+
     def stopService(self):
         print "launching cleaning methods"
         for callback, args, kwargs in self._cleanup:
             callback(*args, **kwargs)
+        self.bridge.disconnect(C.SERVICE_PROFILE)
 
     def run(self):
         reactor.run()
--- a/libervia_server/blog.py	Wed May 07 19:29:10 2014 +0200
+++ b/libervia_server/blog.py	Wed May 07 19:46:58 2014 +0200
@@ -48,9 +48,6 @@
     def __init__(self, host):
         self.host = host
         Resource.__init__(self)
-        # FIXME: this can be move to the beginning of Libervia.startService to avoid an initialization issue
-        if not host.bridge.isConnected(C.SERVICE_PROFILE):
-            host.bridge.connect(C.SERVICE_PROFILE)
 
     def render_GET(self, request):
         if not request.postpath:
--- a/twisted/plugins/libervia.py	Wed May 07 19:29:10 2014 +0200
+++ b/twisted/plugins/libervia.py	Wed May 07 19:46:58 2014 +0200
@@ -56,10 +56,10 @@
 
     def __init__(self):
         """You want to read SàT configuration file now in order to overwrite the hard-coded default values.
-        This is because the priority is (form lowest to highest):
-        - use hard-coded default values
-        - use the values from SàT configuration files
-        - use the values passes on the command line
+        This is because the priority for the usage of the values is (from lowest to highest):
+        - hard-coded default values
+        - values from SàT configuration files
+        - values passed on the command line
         If you do it later: after the command line options have been parsed, there's no good way to know
         if the  options values are the hard-coded ones or if they have been passed on the command line.
         """