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):