Mercurial > libervia-backend
comparison src/stdui/ui_profile_manager.py @ 1697:52af44e745b5
core (stdui[ui_profile_manager]): refactored profile authentication: the workflow is greatly simplified by the use of the new startSession mechanisme and deferedUI
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 27 Nov 2015 16:57:49 +0100 |
parents | 05274b27e90e |
children | d17772b0fe22 |
comparison
equal
deleted
inserted
replaced
1696:9a7a27c44611 | 1697:52af44e745b5 |
---|---|
18 # You should have received a copy of the GNU Affero General Public License | 18 # You should have received a copy of the GNU Affero General Public License |
19 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 19 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | 20 |
21 from sat.core.i18n import D_ | 21 from sat.core.i18n import D_ |
22 from sat.core.constants import Const as C | 22 from sat.core.constants import Const as C |
23 from sat.core.log import getLogger | |
24 log = getLogger(__name__) | |
25 from sat.core import exceptions | |
23 from sat.tools import xml_tools | 26 from sat.tools import xml_tools |
24 from sat.memory.crypto import PasswordHasher | |
25 from sat.memory.memory import ProfileSessions | 27 from sat.memory.memory import ProfileSessions |
26 from twisted.internet import defer | |
27 from twisted.words.protocols.jabber import jid | 28 from twisted.words.protocols.jabber import jid |
28 | 29 |
29 | 30 |
30 class ProfileManager(object): | 31 class ProfileManager(object): |
31 """Manage profiles.""" | 32 """Manage profiles.""" |
36 self._sessions = ProfileSessions() | 37 self._sessions = ProfileSessions() |
37 host.registerCallback(self._authenticateProfile, force_id=C.AUTHENTICATE_PROFILE_ID, with_data=True) | 38 host.registerCallback(self._authenticateProfile, force_id=C.AUTHENTICATE_PROFILE_ID, with_data=True) |
38 host.registerCallback(self._changeXMPPPassword, force_id=C.CHANGE_XMPP_PASSWD_ID, with_data=True) | 39 host.registerCallback(self._changeXMPPPassword, force_id=C.CHANGE_XMPP_PASSWD_ID, with_data=True) |
39 self.__new_xmpp_passwd_id = host.registerCallback(self._changeXMPPPasswordCb, with_data=True) | 40 self.__new_xmpp_passwd_id = host.registerCallback(self._changeXMPPPasswordCb, with_data=True) |
40 | 41 |
42 def _startSessionEb(self, fail, first, profile): | |
43 """Errback method for startSession during profile authentication | |
44 | |
45 @param first(bool): if True, this is the first try and we have tryied empty password | |
46 in this case we ask for a password to the user. | |
47 @param profile(unicode, None): %(doc_profile)s | |
48 must only be used if first is True | |
49 """ | |
50 if first: | |
51 # first call, we ask for the password | |
52 form_ui = xml_tools.XMLUI("form", title=D_('Profile password for {}').format(profile), submit_id='') | |
53 form_ui.addPassword('profile_password', value='') | |
54 d = xml_tools.deferredUI(self.host, form_ui, chained=True) | |
55 d.addCallback(self._authenticateProfile, profile) | |
56 return {'xmlui': form_ui.toXml()} | |
57 | |
58 assert profile is None | |
59 | |
60 if fail.check(exceptions.PasswordError): | |
61 dialog = xml_tools.XMLUI('popup', title=D_('Connection error')) | |
62 dialog.addText(D_("The provided profile password doesn't match.")) | |
63 else: | |
64 log.error(u"Unexpected exceptions: {}".format(fail)) | |
65 dialog = xml_tools.XMLUI('popup', title=D_('Internal error')) | |
66 dialog.addText(D_(u"Internal error: {}".format(fail))) | |
67 return {'xmlui': dialog.toXml(), 'validated': C.BOOL_FALSE} | |
68 | |
41 def _authenticateProfile(self, data, profile): | 69 def _authenticateProfile(self, data, profile): |
42 """Get the data/dialog for connecting a profile | 70 if self.host.memory.isSessionStarted(profile): |
43 | 71 return {'validated': C.BOOL_TRUE} |
44 @param data (dict) | 72 try: |
45 @param profile: %(doc_profile)s | 73 password = data[xml_tools.formEscape('profile_password')] |
46 @return: deferred dict | 74 except KeyError: |
47 """ | 75 # first request, we try empty password |
48 def gotProfileCipher(profile_cipher): | 76 password = '' |
49 if self.host.memory.auth_sessions.profileGetUnique(profile): | 77 first = True |
50 # case 1: profile already authenticated | 78 eb_profile = profile |
51 return {'validated': C.boolConst(True)} | 79 else: |
52 self.profile_ciphers[profile] = profile_cipher | 80 first = False |
53 if 'profile_password' in data: | 81 eb_profile = None |
54 # case 2: password is provided by the caller | 82 d = self.host.memory.startSession(password, profile) |
55 return self._verifyPassword(data, profile) | 83 d.addCallback(lambda dummy: {'validated': C.BOOL_TRUE}) |
56 | 84 d.addErrback(self._startSessionEb, first, eb_profile) |
57 def check_empty_password(verify_password_result): | |
58 validated = C.bool(verify_password_result['validated']) | |
59 if validated: | |
60 # case 3: there's no password for this profile | |
61 return verify_password_result | |
62 | |
63 # case 4: prompt the user for a password | |
64 def xmlui_cb(data_, profile): | |
65 return self._verifyPassword(data_, profile) | |
66 | |
67 callback_id = self.host.registerCallback(xmlui_cb, with_data=True, one_shot=True) | |
68 form_ui = xml_tools.XMLUI("form", title=D_('Profile password for {}').format(profile), submit_id=callback_id) | |
69 form_ui.addPassword('profile_password', value='') | |
70 return {'xmlui': form_ui.toXml()} | |
71 | |
72 check_empty_data = {'profile_password': ''} | |
73 d = self._verifyPassword(check_empty_data, profile) | |
74 return d.addCallback(check_empty_password) | |
75 | |
76 d = self.host.memory.asyncGetStringParamA(C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile) | |
77 d.addCallback(gotProfileCipher) | |
78 d.addErrback(self.getParamError) | |
79 return d | 85 return d |
80 | |
81 def getParamError(self, failure): | |
82 _dialog = xml_tools.XMLUI('popup', title=D_('Error')) | |
83 _dialog.addText(D_("Can't get profile parameter.")) | |
84 return {'xmlui': _dialog.toXml(), 'validated': C.boolConst(False)} | |
85 | |
86 @defer.inlineCallbacks | |
87 def _verifyPassword(self, data, profile): | |
88 """Verify the given password | |
89 | |
90 @param data (dict) | |
91 @param profile: %(doc_profile)s | |
92 @return: deferred dict | |
93 """ | |
94 assert profile in self.profile_ciphers | |
95 | |
96 try: | |
97 profile_password = data[xml_tools.formEscape('profile_password')] | |
98 except KeyError: | |
99 profile_password = data['profile_password'] # not received from a user input | |
100 verified = yield PasswordHasher.verify(profile_password, self.profile_ciphers[profile]) | |
101 if not verified: | |
102 _dialog = xml_tools.XMLUI('popup', title=D_('Connection error')) | |
103 _dialog.addText(D_("The provided profile password doesn't match.")) | |
104 defer.returnValue({'xmlui': _dialog.toXml(), 'validated': C.boolConst(False)}) | |
105 | |
106 yield self.host.memory.newAuthSession(profile_password, profile) | |
107 defer.returnValue({'validated': C.boolConst(True)}) | |
108 | 86 |
109 def _changeXMPPPassword(self, data, profile): | 87 def _changeXMPPPassword(self, data, profile): |
110 session_data = self._sessions.profileGetUnique(profile) | 88 session_data = self._sessions.profileGetUnique(profile) |
111 if not session_data: | 89 if not session_data: |
112 server = self.host.memory.getParamA(C.FORCE_SERVER_PARAM, "Connection", profile_key=profile) | 90 server = self.host.memory.getParamA(C.FORCE_SERVER_PARAM, "Connection", profile_key=profile) |