Commit 6b3739fe authored by Philipp Hörist's avatar Philipp Hörist
Browse files

New SASL

parent b8863e82
......@@ -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 @@ def __init__(self, name):
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 @@ def __init__(self, name):
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 @@ def __init__(self, name):
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 @@ def ssl_certificate_accepted(self):
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 @@ def send_gc_status(self, nick, jid, show, status, auto=False):
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 @@ def generate(self):
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 @@ def get_password(self, account_name):
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 @@ def get_password(self, account_name):
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 @@ def on_password_change(self, new_password, data):
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 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 @@ def handle_event_password_required(self, obj):
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 @@ def on_cancel():
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 @@ def create_core_handlers_list(self):
'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 @@ def create_core_handlers_list(self):
'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],
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment