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:
parent
b8863e82be
commit
6e30d3af64
|
@ -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')],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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],
|
||||
|
|
Loading…
Reference in New Issue