implements Oauth2 mechanism to connect to MSN xmpp server. Fixes #6978
This commit is contained in:
parent
a1e369dde4
commit
6e4bac262c
7 changed files with 71 additions and 1 deletions
|
@ -384,6 +384,9 @@ class Config:
|
||||||
'last_archiving_time': [opt_str, '1970-01-01T00:00:00Z', _('Last time we syncronized with logs from server.')],
|
'last_archiving_time': [opt_str, '1970-01-01T00:00:00Z', _('Last time we syncronized with logs from server.')],
|
||||||
'enable_message_carbons': [ opt_bool, False, _('If enabled and if server supports this feature, Gajim will receive messages sent and received by other resources.')],
|
'enable_message_carbons': [ opt_bool, False, _('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.')],
|
'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 Oauth2 authentication.')],
|
||||||
|
'oauth2_client_id': [ opt_str, '0000000044077801', _('client_id for Oauth2 authentication.')],
|
||||||
|
'oauth2_redirect_url': [ opt_str, 'http%3A%2F%2Fgajim.org%2Fmsnauth%2Findex.cgi', _('redirect_url for Oauth2 authentication.')],
|
||||||
}, {}),
|
}, {}),
|
||||||
'statusmsg': ({
|
'statusmsg': ({
|
||||||
'message': [ opt_str, '' ],
|
'message': [ opt_str, '' ],
|
||||||
|
|
|
@ -40,6 +40,7 @@ import operator
|
||||||
import time
|
import time
|
||||||
import locale
|
import locale
|
||||||
import hmac
|
import hmac
|
||||||
|
import json
|
||||||
|
|
||||||
try:
|
try:
|
||||||
randomsource = random.SystemRandom()
|
randomsource = random.SystemRandom()
|
||||||
|
@ -2492,6 +2493,32 @@ class Connection(CommonConnection, ConnectionHandlers):
|
||||||
|
|
||||||
def get_password(self, callback, type_):
|
def get_password(self, callback, type_):
|
||||||
self.pasword_callback = (callback, type_)
|
self.pasword_callback = (callback, type_)
|
||||||
|
if type_ == 'X-MESSENGER-OAUTH2':
|
||||||
|
client_id = gajim.config.get_per('accounts', self.name,
|
||||||
|
'oauth2_client_id')
|
||||||
|
refresh_token = gajim.config.get_per('accounts', self.name,
|
||||||
|
'oauth2_refresh_token')
|
||||||
|
if refresh_token:
|
||||||
|
renew_URL = 'https://oauth.live.com/token?client_id=' \
|
||||||
|
'%(client_id)s&redirect_uri=https%%3A%%2F%%2Foauth.live.' \
|
||||||
|
'com%%2Fdesktop&grant_type=refresh_token&refresh_token=' \
|
||||||
|
'%(refresh_token)s' % locals()
|
||||||
|
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 = gajim.config.get_per('accounts', self.name,
|
||||||
|
'oauth2_redirect_url')
|
||||||
|
token_URL = 'https://oauth.live.com/authorize?client_id=' \
|
||||||
|
'%(client_id)s&scope=wl.messenger%%20wl.offline_access&' \
|
||||||
|
'response_type=code&redirect_uri=%(script_url)s' % locals()
|
||||||
|
helpers.launch_browser_mailer('url', token_URL)
|
||||||
|
self.disconnect(on_purpose=True)
|
||||||
|
gajim.nec.push_incoming_event(Oauth2CredentialsRequiredEvent(None,
|
||||||
|
conn=self))
|
||||||
|
return
|
||||||
if self.password:
|
if self.password:
|
||||||
self.set_password(self.password)
|
self.set_password(self.password)
|
||||||
return
|
return
|
||||||
|
|
|
@ -1783,6 +1783,10 @@ class PasswordRequiredEvent(nec.NetworkIncomingEvent):
|
||||||
name = 'password-required'
|
name = 'password-required'
|
||||||
base_network_events = []
|
base_network_events = []
|
||||||
|
|
||||||
|
class Oauth2CredentialsRequiredEvent(nec.NetworkIncomingEvent):
|
||||||
|
name = 'oauth2-credentials-required'
|
||||||
|
base_network_events = []
|
||||||
|
|
||||||
class FailedDecryptEvent(nec.NetworkIncomingEvent):
|
class FailedDecryptEvent(nec.NetworkIncomingEvent):
|
||||||
name = 'failed-decrypt'
|
name = 'failed-decrypt'
|
||||||
base_network_events = []
|
base_network_events = []
|
||||||
|
|
|
@ -264,6 +264,12 @@ class SASL(PlugIn):
|
||||||
self._owner._caller.get_password(self.set_password, self.mechanism)
|
self._owner._caller.get_password(self.set_password, self.mechanism)
|
||||||
self.startsasl = SASL_IN_PROCESS
|
self.startsasl = SASL_IN_PROCESS
|
||||||
raise NodeProcessed
|
raise NodeProcessed
|
||||||
|
if 'X-MESSENGER-OAUTH2' in self.mecs:
|
||||||
|
self.mecs.remove('X-MESSENGER-OAUTH2')
|
||||||
|
self.mechanism = 'X-MESSENGER-OAUTH2'
|
||||||
|
self._owner._caller.get_password(self.set_password, self.mechanism)
|
||||||
|
self.startsasl = SASL_IN_PROCESS
|
||||||
|
raise NodeProcessed
|
||||||
self.startsasl = SASL_FAILURE
|
self.startsasl = SASL_FAILURE
|
||||||
log.info('I can only use EXTERNAL, SCRAM-SHA-1, DIGEST-MD5, GSSAPI and '
|
log.info('I can only use EXTERNAL, SCRAM-SHA-1, DIGEST-MD5, GSSAPI and '
|
||||||
'PLAIN mecanisms.')
|
'PLAIN mecanisms.')
|
||||||
|
@ -497,6 +503,10 @@ class SASL(PlugIn):
|
||||||
'\n', '')
|
'\n', '')
|
||||||
node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'},
|
node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'},
|
||||||
payload=[sasl_data])
|
payload=[sasl_data])
|
||||||
|
elif self.mechanism == 'X-MESSENGER-OAUTH2':
|
||||||
|
node = Node('auth', attrs={'xmlns': NS_SASL,
|
||||||
|
'mechanism': 'X-MESSENGER-OAUTH2'})
|
||||||
|
node.addData(password)
|
||||||
self._owner.send(str(node))
|
self._owner.send(str(node))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2165,6 +2165,7 @@ class DoubleInputDialog:
|
||||||
def on_okbutton_clicked(self, widget):
|
def on_okbutton_clicked(self, widget):
|
||||||
user_input1 = self.input_entry1.get_text().decode('utf-8')
|
user_input1 = self.input_entry1.get_text().decode('utf-8')
|
||||||
user_input2 = self.input_entry2.get_text().decode('utf-8')
|
user_input2 = self.input_entry2.get_text().decode('utf-8')
|
||||||
|
self.cancel_handler = None
|
||||||
self.dialog.destroy()
|
self.dialog.destroy()
|
||||||
if not self.ok_handler:
|
if not self.ok_handler:
|
||||||
return
|
return
|
||||||
|
|
|
@ -63,7 +63,7 @@ if os.name == 'nt':
|
||||||
os.environ['PATH'] = ';'.join(new_list)
|
os.environ['PATH'] = ';'.join(new_list)
|
||||||
|
|
||||||
from common import demandimport
|
from common import demandimport
|
||||||
#demandimport.enable()
|
demandimport.enable()
|
||||||
demandimport.ignore += ['gobject._gobject', 'libasyncns', 'i18n',
|
demandimport.ignore += ['gobject._gobject', 'libasyncns', 'i18n',
|
||||||
'logging.NullHandler', 'dbus.glib', 'dbus.service',
|
'logging.NullHandler', 'dbus.glib', 'dbus.service',
|
||||||
'command_system.implementation.standard',
|
'command_system.implementation.standard',
|
||||||
|
|
|
@ -701,6 +701,30 @@ class Interface:
|
||||||
_('Password Required'), text, _('Save password'), ok_handler=on_ok,
|
_('Password Required'), text, _('Save password'), ok_handler=on_ok,
|
||||||
cancel_handler=on_cancel)
|
cancel_handler=on_cancel)
|
||||||
|
|
||||||
|
def handle_oauth2_credentials(self, obj):
|
||||||
|
account = obj.conn.name
|
||||||
|
def on_ok(refresh):
|
||||||
|
gajim.config.set_per('accounts', account, 'oauth2_refresh_token',
|
||||||
|
refresh)
|
||||||
|
st = gajim.config.get_per('accounts', account, 'last_status')
|
||||||
|
msg = helpers.from_one_line(gajim.config.get_per('accounts',
|
||||||
|
account, 'last_status_msg'))
|
||||||
|
gajim.interface.roster.send_status(account, st, msg)
|
||||||
|
del self.pass_dialog[account]
|
||||||
|
|
||||||
|
def on_cancel():
|
||||||
|
gajim.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] = dialogs.InputTextDialog(
|
||||||
|
_('Oauth2 Credentials'), instruction, is_modal=False,
|
||||||
|
ok_handler=on_ok, cancel_handler=on_cancel)
|
||||||
|
|
||||||
def handle_event_roster_info(self, obj):
|
def handle_event_roster_info(self, obj):
|
||||||
#('ROSTER_INFO', account, (jid, name, sub, ask, groups))
|
#('ROSTER_INFO', account, (jid, name, sub, ask, groups))
|
||||||
account = obj.conn.name
|
account = obj.conn.name
|
||||||
|
@ -1412,6 +1436,7 @@ class Interface:
|
||||||
'metacontacts-received': [self.handle_event_metacontacts],
|
'metacontacts-received': [self.handle_event_metacontacts],
|
||||||
'muc-admin-received': [self.handle_event_gc_affiliation],
|
'muc-admin-received': [self.handle_event_gc_affiliation],
|
||||||
'muc-owner-received': [self.handle_event_gc_config],
|
'muc-owner-received': [self.handle_event_gc_config],
|
||||||
|
'oauth2-credentials-required': [self.handle_oauth2_credentials],
|
||||||
'our-show': [self.handle_event_status],
|
'our-show': [self.handle_event_status],
|
||||||
'password-required': [self.handle_event_password_required],
|
'password-required': [self.handle_event_password_required],
|
||||||
'plain-connection': [self.handle_event_plain_connection],
|
'plain-connection': [self.handle_event_plain_connection],
|
||||||
|
|
Loading…
Add table
Reference in a new issue