Mercurial > libervia-web
comparison libervia_server/__init__.py @ 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 | 14c35f7f1ef5 |
comparison
equal
deleted
inserted
replaced
445:c0ff91cabea0 | 446:c406e46fe9c0 |
---|---|
564 Render method with some hacks: | 564 Render method with some hacks: |
565 - if login is requested, try to login with form data | 565 - if login is requested, try to login with form data |
566 - except login, every method is jsonrpc | 566 - except login, every method is jsonrpc |
567 - user doesn't need to be authentified for explicitely listed methods, but must be for all others | 567 - user doesn't need to be authentified for explicitely listed methods, but must be for all others |
568 """ | 568 """ |
569 if request.postpath==['login']: | 569 if request.postpath == ['login']: |
570 return self.login(request) | 570 return self.loginOrRegister(request) |
571 _session = request.getSession() | 571 _session = request.getSession() |
572 parsed = jsonrpclib.loads(request.content.read()) | 572 parsed = jsonrpclib.loads(request.content.read()) |
573 method = parsed.get("method") | 573 method = parsed.get("method") |
574 if method not in ['isRegistered', 'registerParams', 'getMenus']: | 574 if method not in ['isRegistered', 'registerParams', 'getMenus']: |
575 #if we don't call these methods, we need to be identified | 575 #if we don't call these methods, we need to be identified |
579 fault = jsonrpclib.Fault(C.ERRNUM_LIBERVIA, "Not allowed") #FIXME: define some standard error codes for libervia | 579 fault = jsonrpclib.Fault(C.ERRNUM_LIBERVIA, "Not allowed") #FIXME: define some standard error codes for libervia |
580 return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc')) | 580 return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc')) |
581 self.request = request | 581 self.request = request |
582 return jsonrpc.JSONRPC.render(self, request) | 582 return jsonrpc.JSONRPC.render(self, request) |
583 | 583 |
584 def login(self, request): | 584 def loginOrRegister(self, request): |
585 """ | 585 """This method is called with the POST information from the registering form. |
586 this method is called with the POST information from the registering form | 586 |
587 it test if the password is ok, and log in if it's the case, | 587 @param request: request of the register form |
588 else it return an error | 588 @return: a constant indicating the state: |
589 @param request: request of the register formulaire, must have "login" and "password" as arguments | 589 - BAD REQUEST: something is wrong in the request (bad arguments) |
590 @return: A constant indicating the state: | 590 - a return value from self._loginAccount or self._registerNewAccount |
591 - BAD REQUEST: something is wrong in the request (bad arguments, profile_key for login) | 591 """ |
592 - AUTH ERROR: either the profile or the password is wrong | |
593 - ALREADY WAITING: a request has already be made for this profile | |
594 - server.NOT_DONE_YET: the profile is being processed, the return value will be given by self._logged or self._logginError | |
595 """ | |
596 | |
597 try: | 592 try: |
598 if request.args['submit_type'][0] == 'login': | 593 submit_type = request.args['submit_type'][0] |
599 _login = request.args['login'][0] | |
600 if _login.startswith('@'): | |
601 raise Exception('No profile_key allowed') | |
602 _pass = request.args['login_password'][0] | |
603 | |
604 elif request.args['submit_type'][0] == 'register': | |
605 return self._registerNewAccount(request) | |
606 | |
607 else: | |
608 raise Exception('Unknown submit type') | |
609 except KeyError: | 594 except KeyError: |
610 return "BAD REQUEST" | 595 return "BAD REQUEST" |
611 | 596 |
612 _profile_check = self.sat_host.bridge.getProfileName(_login) | 597 if submit_type == 'register': |
613 | 598 return self._registerNewAccount(request) |
614 def profile_pass_cb(_profile_pass): | 599 elif submit_type == 'login': |
615 if not _profile_check or _profile_check != _login or _profile_pass != _pass: | 600 return self._loginAccount(request) |
616 request.write("AUTH ERROR") | 601 return Exception('Unknown submit type') |
617 request.finish() | 602 |
618 return | 603 def _loginAccount(self, request): |
619 | 604 """Try to authenticate the user with the request information. |
620 if self.profiles_waiting.has_key(_login): | 605 @param request: request of the register form |
621 request.write("ALREADY WAITING") | 606 @return: a constant indicating the state: |
622 request.finish() | 607 - BAD REQUEST: something is wrong in the request (bad arguments) |
623 return | 608 - AUTH ERROR: either the profile (login) or the password is wrong |
624 | 609 - ALREADY WAITING: a request has already been submitted for this profile |
625 if self.sat_host.bridge.isConnected(_login): | 610 - server.NOT_DONE_YET: the profile is being processed, the return |
626 request.write(self._logged(_login, request, finish=False)) | 611 value will be given by self._logged or self._logginError |
627 request.finish() | 612 """ |
628 return | 613 try: |
629 | 614 login_ = request.args['login'][0] |
630 self.profiles_waiting[_login] = request | 615 password_ = request.args['login_password'][0] |
631 d = self.asyncBridgeCall("asyncConnect", _login) | 616 except KeyError: |
632 return d | 617 return "BAD REQUEST" |
633 | 618 |
634 def profile_pass_errback(ignore): | 619 if login_.startswith('@'): |
635 log.error("INTERNAL ERROR: can't check profile password") | 620 raise Exception('No profile_key allowed') |
621 | |
622 profile_check = self.sat_host.bridge.getProfileName(login_) | |
623 if not profile_check or profile_check != login_ or not password_: | |
624 # profiles with empty passwords are restricted to local frontends | |
625 return "AUTH ERROR" | |
626 | |
627 if login_ in self.profiles_waiting: | |
628 return "ALREADY WAITING" | |
629 | |
630 def auth_eb(ignore=None): | |
631 self.__cleanWaiting(login_) | |
632 log.info("Profile %s doesn't exist or the submitted password is wrong" % login_) | |
636 request.write("AUTH ERROR") | 633 request.write("AUTH ERROR") |
637 request.finish() | 634 request.finish() |
638 | 635 |
639 d = self.asyncBridgeCall("asyncGetParamA", "Password", "Connection", profile_key=_login) | 636 self.profiles_waiting[login_] = request |
640 d.addCallbacks(profile_pass_cb, profile_pass_errback) | 637 d = self.asyncBridgeCall("asyncConnect", login_, password_) |
638 d.addCallbacks(lambda connected: self._logged(login_, request) if connected else None, auth_eb) | |
641 | 639 |
642 return server.NOT_DONE_YET | 640 return server.NOT_DONE_YET |
643 | 641 |
644 def _registerNewAccount(self, request): | 642 def _registerNewAccount(self, request): |
645 """Create a new account, or return error | 643 """Create a new account, or return error |
646 @param request: initial login request | 644 @param request: request of the register form |
647 @return: "REGISTRATION" in case of success""" | 645 @return: a constant indicating the state: |
648 #TODO: must be moved in SàT core | 646 - BAD REQUEST: something is wrong in the request (bad arguments) |
649 | 647 - REGISTRATION: new account has been successfully registered |
648 - ALREADY EXISTS: the given profile already exists | |
649 - INTERNAL or 'Unknown error (...)' | |
650 - server.NOT_DONE_YET: the profile is being processed, the return | |
651 value will be given later (one of those previously described) | |
652 """ | |
650 try: | 653 try: |
651 profile = login = request.args['register_login'][0] | 654 profile = login = request.args['register_login'][0] |
652 password = request.args['register_password'][0] #FIXME: password is ignored so far | 655 password = request.args['register_password'][0] |
653 email = request.args['email'][0] | 656 email = request.args['email'][0] |
654 except KeyError: | 657 except KeyError: |
655 return "BAD REQUEST" | 658 return "BAD REQUEST" |
656 if not re.match(r'^[a-z0-9_-]+$', login, re.IGNORECASE) or \ | 659 if not re.match(r'^[a-z0-9_-]+$', login, re.IGNORECASE) or \ |
657 not re.match(r'^.+@.+\..+', email, re.IGNORECASE) or \ | 660 not re.match(r'^.+@.+\..+', email, re.IGNORECASE) or \ |
683 try: | 686 try: |
684 del self.profiles_waiting[login] | 687 del self.profiles_waiting[login] |
685 except KeyError: | 688 except KeyError: |
686 pass | 689 pass |
687 | 690 |
688 def _logged(self, profile, request, finish=True): | 691 def _logged(self, profile, request): |
689 """Set everything when a user just logged | 692 """Set everything when a user just logged in |
690 and return "LOGGED" to the requester""" | 693 |
691 def result(answer): | 694 @param profile |
692 if finish: | 695 @param request |
693 request.write(answer) | 696 @return: a constant indicating the state: |
694 request.finish() | 697 - LOGGED |
695 else: | 698 - SESSION_ACTIVE |
696 return answer | 699 """ |
697 | |
698 self.__cleanWaiting(profile) | 700 self.__cleanWaiting(profile) |
699 _session = request.getSession() | 701 _session = request.getSession() |
700 sat_session = ISATSession(_session) | 702 sat_session = ISATSession(_session) |
701 if sat_session.profile: | 703 if sat_session.profile: |
702 log.error (('/!\\ Session has already a profile, this should NEVER happen !')) | 704 log.error(('/!\\ Session has already a profile, this should NEVER happen!')) |
703 return result('SESSION_ACTIVE') | 705 request.write('SESSION_ACTIVE') |
706 request.finish() | |
707 return | |
704 sat_session.profile = profile | 708 sat_session.profile = profile |
705 self.sat_host.prof_connected.add(profile) | 709 self.sat_host.prof_connected.add(profile) |
706 | 710 |
707 def onExpire(): | 711 def onExpire(): |
708 log.info ("Session expired (profile=%s)" % (profile,)) | 712 log.info("Session expired (profile=%s)" % (profile,)) |
709 try: | 713 try: |
710 #We purge the queue | 714 #We purge the queue |
711 del self.sat_host.signal_handler.queue[profile] | 715 del self.sat_host.signal_handler.queue[profile] |
712 except KeyError: | 716 except KeyError: |
713 pass | 717 pass |
714 #and now we disconnect the profile | 718 #and now we disconnect the profile |
715 self.sat_host.bridge.disconnect(profile) | 719 self.sat_host.bridge.disconnect(profile) |
716 | 720 |
717 _session.notifyOnExpire(onExpire) | 721 _session.notifyOnExpire(onExpire) |
718 | 722 |
719 return result('LOGGED') | 723 request.write('LOGGED') |
724 request.finish() | |
720 | 725 |
721 def _logginError(self, login, request, error_type): | 726 def _logginError(self, login, request, error_type): |
722 """Something went wrong during loggin, return an error""" | 727 """Something went wrong during logging in |
728 @return: error | |
729 """ | |
723 self.__cleanWaiting(login) | 730 self.__cleanWaiting(login) |
724 return error_type | 731 return error_type |
725 | 732 |
726 def jsonrpc_isConnected(self): | 733 def jsonrpc_isConnected(self): |
727 _session = self.request.getSession() | 734 _session = self.request.getSession() |
838 self.queue[profile] = [] | 845 self.queue[profile] = [] |
839 self.queue[profile].append((function_name, args[:-1])) | 846 self.queue[profile].append((function_name, args[:-1])) |
840 return genericCb | 847 return genericCb |
841 | 848 |
842 def connected(self, profile): | 849 def connected(self, profile): |
843 assert(self.register) #register must be plugged | 850 assert(self.register) # register must be plugged |
844 request = self.register.getWaitingRequest(profile) | 851 request = self.register.getWaitingRequest(profile) |
845 if request: | 852 if request: |
846 self.register._logged(profile, request) | 853 self.register._logged(profile, request) |
847 | 854 |
848 def disconnected(self, profile): | 855 def disconnected(self, profile): |
989 ['port_https', 's', 8443, 'The port number to listen HTTPS on.', int], | 996 ['port_https', 's', 8443, 'The port number to listen HTTPS on.', int], |
990 ['port_https_ext', 'e', 0, 'The external port number used for HTTPS (0 means port_https value).', int], | 997 ['port_https_ext', 'e', 0, 'The external port number used for HTTPS (0 means port_https value).', int], |
991 ['ssl_certificate', 'c', 'libervia.pem', 'PEM certificate with both private and public parts.', str], | 998 ['ssl_certificate', 'c', 'libervia.pem', 'PEM certificate with both private and public parts.', str], |
992 ['redirect_to_https', 'r', 1, 'automatically redirect from HTTP to HTTPS.', int], | 999 ['redirect_to_https', 'r', 1, 'automatically redirect from HTTP to HTTPS.', int], |
993 ['security_warning', 'w', 1, 'warn user that he is about to connect on HTTP.', int], | 1000 ['security_warning', 'w', 1, 'warn user that he is about to connect on HTTP.', int], |
1001 ['passphrase', 'k', '', u"passphrase for the SàT profile named '%s'" % C.SERVICE_PROFILE, str], | |
994 ] | 1002 ] |
995 | 1003 |
996 def __init__(self, *args, **kwargs): | 1004 def __init__(self, *args, **kwargs): |
997 if not kwargs: | 1005 if not kwargs: |
998 # During the loading of the twisted plugins, we just need the default values. | 1006 # During the loading of the twisted plugins, we just need the default values. |
1007 if not self.port_https_ext: | 1015 if not self.port_https_ext: |
1008 self.port_https_ext = self.port_https | 1016 self.port_https_ext = self.port_https |
1009 self.ssl_certificate = kwargs['ssl_certificate'] | 1017 self.ssl_certificate = kwargs['ssl_certificate'] |
1010 self.redirect_to_https = kwargs['redirect_to_https'] | 1018 self.redirect_to_https = kwargs['redirect_to_https'] |
1011 self.security_warning = kwargs['security_warning'] | 1019 self.security_warning = kwargs['security_warning'] |
1020 self.passphrase = kwargs['passphrase'] | |
1012 self._cleanup = [] | 1021 self._cleanup = [] |
1013 root = ProtectedFile(C.LIBERVIA_DIR) | 1022 root = ProtectedFile(C.LIBERVIA_DIR) |
1014 self.signal_handler = SignalHandler(self) | 1023 self.signal_handler = SignalHandler(self) |
1015 _register = Register(self) | 1024 _register = Register(self) |
1016 _upload_radiocol = UploadManagerRadioCol(self) | 1025 _upload_radiocol = UploadManagerRadioCol(self) |
1067 @param *args: list of arguments of the callback | 1076 @param *args: list of arguments of the callback |
1068 @param **kwargs: list of keyword arguments of the callback""" | 1077 @param **kwargs: list of keyword arguments of the callback""" |
1069 self._cleanup.insert(0, (callback, args, kwargs)) | 1078 self._cleanup.insert(0, (callback, args, kwargs)) |
1070 | 1079 |
1071 def startService(self): | 1080 def startService(self): |
1081 """Connect the profile for Libervia and start the HTTP(S) server(s)""" | |
1082 def eb(e): | |
1083 log.error(_("Connection failed: %s") % e) | |
1084 self.stop() | |
1085 | |
1072 def initOk(dummy): | 1086 def initOk(dummy): |
1073 if self.connection_type in ('https', 'both'): | 1087 if not self.bridge.isConnected(C.SERVICE_PROFILE): |
1074 if not ssl_available: | 1088 self.bridge.asyncConnect(C.SERVICE_PROFILE, self.passphrase, |
1075 raise(ImportError(_("Python module pyOpenSSL is not installed!"))) | 1089 callback=self._startService, errback=eb) |
1076 try: | 1090 |
1077 with open(os.path.expanduser(self.ssl_certificate)) as keyAndCert: | |
1078 try: | |
1079 cert = ssl.PrivateCertificate.loadPEM(keyAndCert.read()) | |
1080 except OpenSSL.crypto.Error as e: | |
1081 log.error(_("The file '%s' must contain both private and public parts of the certificate") % self.ssl_certificate) | |
1082 raise e | |
1083 except IOError as e: | |
1084 log.error(_("The file '%s' doesn't exist") % self.ssl_certificate) | |
1085 raise e | |
1086 reactor.listenSSL(self.port_https, self.site, cert.options()) | |
1087 if self.connection_type in ('http', 'both'): | |
1088 if self.connection_type == 'both' and self.redirect_to_https: | |
1089 reactor.listenTCP(self.port, server.Site(RedirectToHTTPS(self.port, self.port_https_ext))) | |
1090 else: | |
1091 reactor.listenTCP(self.port, self.site) | |
1092 self.initialised.addCallback(initOk) | 1091 self.initialised.addCallback(initOk) |
1092 | |
1093 def _startService(self, dummy): | |
1094 """Actually start the HTTP(S) server(s) after the profile for Libervia is connected""" | |
1095 if self.connection_type in ('https', 'both'): | |
1096 if not ssl_available: | |
1097 raise(ImportError(_("Python module pyOpenSSL is not installed!"))) | |
1098 try: | |
1099 with open(os.path.expanduser(self.ssl_certificate)) as keyAndCert: | |
1100 try: | |
1101 cert = ssl.PrivateCertificate.loadPEM(keyAndCert.read()) | |
1102 except OpenSSL.crypto.Error as e: | |
1103 log.error(_("The file '%s' must contain both private and public parts of the certificate") % self.ssl_certificate) | |
1104 raise e | |
1105 except IOError as e: | |
1106 log.error(_("The file '%s' doesn't exist") % self.ssl_certificate) | |
1107 raise e | |
1108 reactor.listenSSL(self.port_https, self.site, cert.options()) | |
1109 if self.connection_type in ('http', 'both'): | |
1110 if self.connection_type == 'both' and self.redirect_to_https: | |
1111 reactor.listenTCP(self.port, server.Site(RedirectToHTTPS(self.port, self.port_https_ext))) | |
1112 else: | |
1113 reactor.listenTCP(self.port, self.site) | |
1093 | 1114 |
1094 def stopService(self): | 1115 def stopService(self): |
1095 print "launching cleaning methods" | 1116 print "launching cleaning methods" |
1096 for callback, args, kwargs in self._cleanup: | 1117 for callback, args, kwargs in self._cleanup: |
1097 callback(*args, **kwargs) | 1118 callback(*args, **kwargs) |
1119 self.bridge.disconnect(C.SERVICE_PROFILE) | |
1098 | 1120 |
1099 def run(self): | 1121 def run(self): |
1100 reactor.run() | 1122 reactor.run() |
1101 | 1123 |
1102 def stop(self): | 1124 def stop(self): |