# HG changeset patch # User souliane # Date 1399471418 -7200 # Node ID b262ae6d53afc5be0f44c5a21717edb1bf82a1db # Parent e90125d07072fd1d70377363957321e8e9ff0e2d stdui: add ui_profile_manager to interact with frontends when profile authentication is needed diff -r e90125d07072 -r b262ae6d53af src/core/constants.py --- a/src/core/constants.py Mon May 12 17:51:38 2014 +0200 +++ b/src/core/constants.py Wed May 07 16:03:38 2014 +0200 @@ -110,6 +110,10 @@ LOG_LEVELS = (LOG_LVL_DEBUG, LOG_LVL_INFO, LOG_LVL_WARNING, LOG_LVL_ERROR, LOG_LVL_CRITICAL) + # HARD-CODED ACTIONS IDS + AUTHENTICATE_PROFILE_ID = u'b03bbfa8-a4ae-4734-a248-06ce6c7cf562' + + ## Misc ## SAVEFILE_DATABASE = APP_NAME_FILE + ".db" IQ_SET = '/iq[@type="set"]' diff -r e90125d07072 -r b262ae6d53af src/core/sat_main.py --- a/src/core/sat_main.py Mon May 12 17:51:38 2014 +0200 +++ b/src/core/sat_main.py Wed May 07 16:03:38 2014 +0200 @@ -33,7 +33,7 @@ from sat.memory.memory import Memory from sat.memory.crypto import PasswordHasher from sat.tools.misc import TriggerManager -from sat.stdui import ui_contact_list +from sat.stdui import ui_contact_list, ui_profile_manager from glob import glob from uuid import uuid4 import sys @@ -146,6 +146,7 @@ log.info(_("Memory initialised")) self._import_plugins() ui_contact_list.ContactList(self) + ui_profile_manager.ProfileManager(self) self._initialised.callback(None) def _import_plugins(self): diff -r e90125d07072 -r b262ae6d53af src/stdui/ui_profile_manager.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stdui/ui_profile_manager.py Wed May 07 16:03:38 2014 +0200 @@ -0,0 +1,104 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# SAT standard user interface for managing contacts +# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org) +# Copyright (C) 2013, 2014 Adrien Cossa (souliane@mailoo.org) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from sat.core.i18n import _, D_ +from sat.core.constants import Const as C +from sat.tools import xml_tools +from sat.memory.crypto import PasswordHasher +from twisted.internet import defer + + +class ProfileManager(object): + """Manage profiles.""" + + def __init__(self, host): + self.host = host + self.profile_ciphers = {} + host.registerCallback(self._authenticateProfile, force_id=C.AUTHENTICATE_PROFILE_ID, with_data=True) + + def _authenticateProfile(self, data, profile): + """Get the data/dialog for connecting a profile + + @param data (dict) + @param profile: %(doc_profile)s + @return: deferred dict + """ + def gotProfileCipher(profile_cipher): + if self.host.memory.auth_sessions.profileGetUnique(profile): + # case 1: profile already authenticated + return {'authenticated_profile': profile, 'caller': data['caller']} + self.profile_ciphers[profile] = profile_cipher + if 'profile_password' in data: + # case 2: password is provided by the caller + return self._verifyPassword(data, profile) + + def check_empty_password(empty_password_result): + if 'authenticated_profile' in empty_password_result: + # case 3: there's no password for this profile + return empty_password_result + + # case 4: prompt the user for a password + def xmlui_cb(data_, profile): + data_['caller'] = data['caller'] + return self._verifyPassword(data_, profile) + + callback_id = self.host.registerCallback(xmlui_cb, with_data=True, one_shot=True) + form_ui = xml_tools.XMLUI("form", title=D_('Profile password for %s') % profile, submit_id=callback_id) + form_ui.addPassword('profile_password', value='') + return {'xmlui': form_ui.toXml()} + + check_empty_data = {'profile_password': '', 'caller': data['caller']} + d = self._verifyPassword(check_empty_data, profile) + return d.addCallback(check_empty_password) + + assert(data['caller']) + d = self.host.memory.asyncGetStringParamA(C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile) + d.addCallback(gotProfileCipher) + d.addErrback(self.getParamError) + return d + + def getParamError(self): + _dialog = xml_tools.XMLUI('popup', title=D_('Error')) + _dialog.addText(_("Can't get profile parameter.")) + return {'xmlui': _dialog.toXml()} + + @defer.inlineCallbacks + def _verifyPassword(self, data, profile): + """Verify the given password + + @param data (dict) + @param profile: %(doc_profile)s + @return: deferred dict + """ + assert(profile in self.profile_ciphers) + assert(data['caller']) + + try: + profile_password = data[xml_tools.formEscape('profile_password')] + except KeyError: + profile_password = data['profile_password'] # not received from a user input + verified = yield PasswordHasher.verify(profile_password, self.profile_ciphers[profile]) + if not verified: + _dialog = xml_tools.XMLUI('popup', title=D_('Error')) + _dialog.addText(_("The provided profile password doesn't match.")) + defer.returnValue({'xmlui': _dialog.toXml()}) + + yield self.host.memory.newAuthSession(profile_password, profile) + defer.returnValue({'authenticated_profile': profile, 'caller': data['caller']})