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)