Adapt to nbxmpp API changes

- Remove support for OAUTH2
- Remove insecure password dialog, its impossible now that a user can connect plain by mistake
- Refactor requesting the password
- Add possibility to delete passwords
This commit is contained in:
Philipp Hörist 2018-11-27 18:15:02 +01:00
parent b8863e82be
commit 6e30d3af64
6 changed files with 66 additions and 169 deletions

View File

@ -391,9 +391,6 @@ class Config:
'subscription_request_msg': [opt_str, '', _('Message that is sent to contacts you want to add')],
'enable_message_carbons': [opt_bool, True, _('If enabled and if server supports this feature, Gajim will receive messages sent and received by other resources.')],
'ft_send_local_ips': [opt_bool, True, _('If enabled, Gajim will send your local IPs so your contact can connect to your machine to transfer files.')],
'oauth2_refresh_token': [opt_str, '', _('Latest token for OAuth 2.0 authentication.')],
'oauth2_client_id': [opt_str, '0000000044077801', _('client_id for OAuth 2.0 authentication.')],
'oauth2_redirect_url': [opt_str, 'https%3A%2F%2Fgajim.org%2Fmsnauth%2Findex.cgi', _('redirect_url for OAuth 2.0 authentication.')],
'opened_chat_controls': [opt_str, '', _('Space separated list of JIDs for which chat window will be re-opened on next startup.')],
'recent_groupchats': [opt_str, ''],
'httpupload_verify': [opt_bool, True, _('HTTP Upload: Enable HTTPS Verification')],

View File

@ -87,7 +87,7 @@ class CommonConnection:
self.connected = 0
self.connection = None # xmpppy ClientCommon instance
self.is_zeroconf = False
self.password = ''
self.password = None
self.server_resource = self._compute_resource()
self.gpg = None
self.USE_GPG = False
@ -500,7 +500,6 @@ class Connection(CommonConnection, ConnectionHandlers):
self.new_account_info = None
self.new_account_form = None
self.last_sent = []
self.password = passwords.get_password(name)
self._unregister_account = False
self._unregister_account_cb = None
@ -510,7 +509,6 @@ class Connection(CommonConnection, ConnectionHandlers):
self.register_supported = False
# Do we auto accept insecure connection
self.connection_auto_accepted = False
self.pasword_callback = None
self.on_connect_success = None
self.on_connect_failure = None
@ -1273,25 +1271,45 @@ class Connection(CommonConnection, ConnectionHandlers):
return
log.info('SSL Cert accepted')
name = None
if not app.config.get_per('accounts', self.name, 'anonymous_auth'):
name = app.config.get_per('accounts', self.name, 'name')
self._auth()
def _get_password(self, mechanism, on_password):
if not mechanism.startswith('SCRAM') and not mechanism == 'PLAIN':
log.error('No password method for %s known', mechanism)
return
if self.password is not None:
# Passord already known
on_password(self.password)
return
pass_saved = app.config.get_per('accounts', self.name, 'savepass')
if pass_saved:
# Request password from keyring only if the user chose to save
# his password
self.password = passwords.get_password(self.name)
if self.password is not None:
on_password(self.password)
else:
app.nec.push_incoming_event(PasswordRequiredEvent(
None, conn=self, on_password=on_password))
def _auth(self):
self._register_handlers(self.connection, self._current_type)
auth_mechs = app.config.get_per(
'accounts', self.name, 'authentication_mechanisms').split()
for mech in auth_mechs:
if mech not in nbxmpp.auth_nb.SASL_AUTHENTICATION_MECHANISMS:
log.warning('Unknown authentication mechanisms %s', mech)
if not auth_mechs:
auth_mechs = None
if app.config.get_per('accounts', self.name, 'anonymous_auth'):
name = None
auth_mechs = {'ANONYMOUS'}
else:
auth_mechs = set(auth_mechs)
self.connection.auth(user=name,
password=self.password,
name = app.config.get_per('accounts', self.name, 'name')
auth_mechs = app.config.get_per(
'accounts', self.name, 'authentication_mechanisms').split()
auth_mechs = set(auth_mechs) if auth_mechs else None
self.connection.auth(name,
get_password=self._get_password,
resource=self.server_resource,
sasl=True,
auth_mechs=auth_mechs)
def _register_handlers(self, con, con_type):
@ -1795,66 +1813,6 @@ class Connection(CommonConnection, ConnectionHandlers):
caps=ptype != 'unavailable',
idle_time=idle_time)
def get_password(self, callback, type_):
if app.config.get_per('accounts', self.name, 'anonymous_auth') and \
type_ != 'ANONYMOUS':
app.nec.push_incoming_event(
NonAnonymousServerErrorEvent(None, conn=self))
self.disconnect(reconnect=False)
return
self.pasword_callback = (callback, type_)
if type_ == 'X-MESSENGER-OAUTH2':
client_id = app.config.get_per('accounts', self.name,
'oauth2_client_id')
refresh_token = app.config.get_per('accounts', self.name,
'oauth2_refresh_token')
if refresh_token:
renew_url = (
'https://oauth.live.com/token?client_id='
'%s&redirect_uri=https%%3A%%2F%%2Foauth.live.'
'com%%2Fdesktop&grant_type=refresh_token&'
'refresh_token=%s') % (client_id, refresh_token)
result = helpers.download_image(self.name, {'src': renew_url})[0]
if result:
dict_ = json.loads(result)
if 'access_token' in dict_:
self.set_password(dict_['access_token'])
return
script_url = app.config.get_per('accounts', self.name,
'oauth2_redirect_url')
token_url = (
'https://oauth.live.com/authorize?client_id='
'%s&scope=wl.messenger%%20wl.offline_access&'
'response_type=code&redirect_uri=%s') % (client_id, script_url)
helpers.launch_browser_mailer('url', token_url)
self.disconnect(reconnect=False)
app.nec.push_incoming_event(
Oauth2CredentialsRequiredEvent(None, conn=self))
return
if self.password:
self.set_password(self.password)
return
app.nec.push_incoming_event(PasswordRequiredEvent(None, conn=self))
def set_password(self, password):
self.password = password
if self.pasword_callback:
callback, type_ = self.pasword_callback
if self._current_type == 'plain' and type_ == 'PLAIN' and \
app.config.get_per('accounts', self.name,
'warn_when_insecure_password'):
app.nec.push_incoming_event(InsecurePasswordEvent(None,
conn=self))
return
callback(password)
self.pasword_callback = None
def accept_insecure_password(self):
if self.pasword_callback:
callback = self.pasword_callback[0]
callback(self.password)
self.pasword_callback = None
def unregister_account(self, on_remove_success):
self._unregister_account = True
self._unregister_account_cb = on_remove_success

View File

@ -681,9 +681,6 @@ class PEPReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
class PlainConnectionEvent(nec.NetworkIncomingEvent):
name = 'plain-connection'
class InsecurePasswordEvent(nec.NetworkIncomingEvent):
name = 'insecure-password'
class InsecureSSLConnectionEvent(nec.NetworkIncomingEvent):
name = 'insecure-ssl-connection'
@ -723,9 +720,6 @@ class ZeroconfNameConflictEvent(nec.NetworkIncomingEvent):
class PasswordRequiredEvent(nec.NetworkIncomingEvent):
name = 'password-required'
class Oauth2CredentialsRequiredEvent(nec.NetworkIncomingEvent):
name = 'oauth2-credentials-required'
class SignedInEvent(nec.NetworkIncomingEvent):
name = 'signed-in'

View File

@ -43,10 +43,14 @@ class PasswordStorage:
def get_password(self, account_name):
"""Return the password for account_name, or None if not found."""
raise NotImplementedError
def save_password(self, account_name, password):
"""Save password for account_name. Return a bool indicating success."""
raise NotImplementedError
def delete_password(self, account_name):
"""Delete password for account_name. Return a bool indicating success."""
raise NotImplementedError
class SecretPasswordStorage(PasswordStorage):
""" Store password using Keyring """
@ -69,6 +73,10 @@ class SecretPasswordStorage(PasswordStorage):
log.debug('getting password')
return self.keyring.get_password('gajim', account_name)
def delete_password(self, account_name):
return self.keyring.delete_password('gajim', account_name)
class PasswordStorageManager(PasswordStorage):
"""Access all the implemented password storage backends, knowing which ones
are available and which we prefer to use.
@ -118,17 +126,27 @@ class PasswordStorageManager(PasswordStorage):
return pw
def save_password(self, account_name, password):
if account_name in app.connections:
app.connections[account_name].password = password
if not app.config.get_per('accounts', account_name, 'savepass'):
return True
if self.preferred_backend:
if self.preferred_backend.save_password(account_name, password):
app.config.set_per('accounts', account_name, 'password',
self.preferred_backend.identifier)
if account_name in app.connections:
app.connections[account_name].password = password
return True
else:
app.config.set_per('accounts', account_name, 'password', password)
return True
app.config.set_per('accounts', account_name, 'password', password)
def delete_password(self, account_name):
if account_name in app.connections:
app.connections[account_name].password = password
app.connections[account_name].password = None
if not self.preferred_backend:
self.preferred_backend.delete_password(account_name)
else:
app.config.set_per('accounts', account_name, 'password', None)
return True
def set_preferred_backend(self):
@ -148,7 +166,8 @@ def get_storage():
def get_password(account_name):
return get_storage().get_password(account_name)
def delete_password(account_name):
return get_storage().delete_password(account_name)
def save_password(account_name, password):
if account_name in app.connections:
app.connections[account_name].set_password(password)
return get_storage().save_password(account_name, password)

View File

@ -810,4 +810,4 @@ class LoginDialog(OptionsDialog):
def on_destroy(self, *args):
savepass = app.config.get_per('accounts', self.account, 'savepass')
if not savepass:
passwords.save_password(self.account, '')
passwords.delete_password(self.account)

View File

@ -107,7 +107,6 @@ from gajim.gtk.dialogs import WarningDialog
from gajim.gtk.dialogs import InformationDialog
from gajim.gtk.dialogs import InputDialog
from gajim.gtk.dialogs import YesNoDialog
from gajim.gtk.dialogs import InputTextDialog
from gajim.gtk.dialogs import PlainConnectionDialog
from gajim.gtk.dialogs import SSLErrorDialog
from gajim.gtk.dialogs import ConfirmationDialogDoubleCheck
@ -713,45 +712,19 @@ class Interface:
text = _('Enter your password for account %s') % account
def on_ok(passphrase, save):
if save:
app.config.set_per('accounts', account, 'savepass', True)
passwords.save_password(account, passphrase)
obj.conn.set_password(passphrase)
app.config.set_per('accounts', account, 'savepass', save)
passwords.save_password(account, passphrase)
obj.on_password(passphrase)
del self.pass_dialog[account]
def on_cancel():
self.roster.set_state(account, 'offline')
self.roster.update_status_combobox()
obj.conn.disconnect(reconnect=False, immediately=True)
del self.pass_dialog[account]
self.pass_dialog[account] = dialogs.PassphraseDialog(
_('Password Required'), text, _('Save password'), ok_handler=on_ok,
cancel_handler=on_cancel)
def handle_oauth2_credentials(self, obj):
account = obj.conn.name
def on_ok(refresh):
app.config.set_per('accounts', account, 'oauth2_refresh_token',
refresh)
st = app.config.get_per('accounts', account, 'last_status')
msg = helpers.from_one_line(app.config.get_per('accounts',
account, 'last_status_msg'))
app.interface.roster.send_status(account, st, msg)
del self.pass_dialog[account]
def on_cancel():
app.config.set_per('accounts', account, 'oauth2_refresh_token',
'')
self.roster.set_state(account, 'offline')
self.roster.update_status_combobox()
del self.pass_dialog[account]
instruction = _('Please copy / paste the refresh token from the website'
' that has just been opened.')
self.pass_dialog[account] = InputTextDialog(
_('Oauth2 Credentials'), instruction, is_modal=False,
ok_handler=on_ok, cancel_handler=on_cancel)
def handle_event_roster_info(self, obj):
#('ROSTER_INFO', account, (jid, name, sub, ask, groups))
account = obj.conn.name
@ -1454,48 +1427,6 @@ class Interface:
checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
is_modal=False)
def handle_event_insecure_password(self, obj):
# ('INSECURE_PASSWORD', account, ())
def on_ok(is_checked):
if not is_checked[0]:
on_cancel()
return
del self.instances[obj.conn.name]['online_dialog']\
['insecure_password']
if is_checked[1]:
app.config.set_per('accounts', obj.conn.name,
'warn_when_insecure_password', False)
if obj.conn.connected == 0:
# We have been disconnecting (too long time since window is
# opened)
# re-connect with auto-accept
obj.conn.connection_auto_accepted = True
show, msg = obj.conn.continue_connect_info[:2]
self.roster.send_status(obj.conn.name, show, msg)
return
obj.conn.accept_insecure_password()
def on_cancel():
del self.instances[obj.conn.name]['online_dialog']\
['insecure_password']
obj.conn.disconnect(reconnect=False)
app.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
show='offline'))
pritext = _('Insecure connection')
sectext = _('You are about to send your password unencrypted on an '
'insecure connection. Are you sure you want to do that?')
checktext1 = _('Yes, I really want to connect insecurely')
checktext2 = _('_Do not ask me again')
if 'insecure_password' in self.instances[obj.conn.name]\
['online_dialog']:
self.instances[obj.conn.name]['online_dialog']\
['insecure_password'].destroy()
self.instances[obj.conn.name]['online_dialog']['insecure_password'] = \
ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
is_modal=False)
def create_core_handlers_list(self):
self.handlers = {
'DB_ERROR': [self.handle_event_db_error],
@ -1515,7 +1446,6 @@ class Interface:
'gpg-trust-key': [self.handle_event_gpg_trust_key],
'http-auth-received': [self.handle_event_http_auth],
'information': [self.handle_event_information],
'insecure-password': [self.handle_event_insecure_password],
'insecure-ssl-connection': \
[self.handle_event_insecure_ssl_connection],
'iq-error-received': [self.handle_event_iq_error],
@ -1530,7 +1460,6 @@ class Interface:
'message-sent': [self.handle_event_msgsent],
'metacontacts-received': [self.handle_event_metacontacts],
'muc-owner-received': [self.handle_event_gc_config],
'oauth2-credentials-required': [self.handle_oauth2_credentials],
'our-show': [self.handle_event_status],
'password-required': [self.handle_event_password_required],
'plain-connection': [self.handle_event_plain_connection],