# HG changeset patch # User souliane # Date 1399484818 -7200 # Node ID c406e46fe9c0bb4c97bf80a65bdd2d2cf349ade3 # Parent c0ff91cabea03c3928f603659098b9992023915a server_side: update the connection mechanism to handle profile passwords diff -r c0ff91cabea0 -r c406e46fe9c0 libervia_server/__init__.py --- 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() diff -r c0ff91cabea0 -r c406e46fe9c0 libervia_server/blog.py --- 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: diff -r c0ff91cabea0 -r c406e46fe9c0 twisted/plugins/libervia.py --- 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. """