Python 3 port:

/!\ this is a huge commit
/!\ starting from this commit, SàT is needs Python 3.6+
/!\ SàT maybe be instable or some feature may not work anymore, this will improve with time

This patch port backend, bridge and frontends to Python 3.

Roughly this has been done this way:

- 2to3 tools has been applied (with python 3.7)
- all references to python2 have been replaced with python3 (notably shebangs)
- fixed files not handled by 2to3 (notably the shell script)
- several manual fixes
- fixed issues reported by Python 3 that where not handled in Python 2
- replaced "async" with "async_" when needed (it's a reserved word from Python 3.7)
- replaced zope's "implements" with @implementer decorator
- temporary hack to handle data pickled in database, as str or bytes may be returned,
  to be checked later
- fixed hash comparison for password
- removed some code which is not needed anymore with Python 3
- deactivated some code which needs to be checked (notably certificate validation)
- tested with jp, fixed reported issues until some basic commands worked
- ported Primitivus (after porting dependencies like urwid satext)
- more manual fixes
This commit is contained in:
Goffi 2019-08-13 19:08:41 +02:00
commit fbfe96c505

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# SAT plugin for file tansfer
@ -41,23 +41,23 @@ PLUGIN_INFO = {
C.PI_RECOMMENDATIONS: ["IDENTITY"],
C.PI_MAIN: "InvitationsPlugin",
C.PI_HANDLER: "no",
C.PI_DESCRIPTION: _(u"""invitation of people without XMPP account""")
C.PI_DESCRIPTION: _("""invitation of people without XMPP account""")
}
SUFFIX_MAX = 5
INVITEE_PROFILE_TPL = u"guest@@{uuid}"
KEY_ID = u'id'
KEY_JID = u'jid'
KEY_CREATED = u'created'
KEY_LAST_CONNECTION = u'last_connection'
KEY_GUEST_PROFILE = u'guest_profile'
KEY_PASSWORD = u'password'
KEY_EMAILS_EXTRA = u'emails_extra'
EXTRA_RESERVED = {KEY_ID, KEY_JID, KEY_CREATED, u'jid_', u'jid', KEY_LAST_CONNECTION,
INVITEE_PROFILE_TPL = "guest@@{uuid}"
KEY_ID = 'id'
KEY_JID = 'jid'
KEY_CREATED = 'created'
KEY_LAST_CONNECTION = 'last_connection'
KEY_GUEST_PROFILE = 'guest_profile'
KEY_PASSWORD = 'password'
KEY_EMAILS_EXTRA = 'emails_extra'
EXTRA_RESERVED = {KEY_ID, KEY_JID, KEY_CREATED, 'jid_', 'jid', KEY_LAST_CONNECTION,
KEY_GUEST_PROFILE, KEY_PASSWORD, KEY_EMAILS_EXTRA}
DEFAULT_SUBJECT = D_(u"You have been invited by {host_name} to {app_name}")
DEFAULT_BODY = D_(u"""Hello {name}!
DEFAULT_SUBJECT = D_("You have been invited by {host_name} to {app_name}")
DEFAULT_BODY = D_("""Hello {name}!
You have received an invitation from {host_name} to participate to "{app_name}".
To join, you just have to click on the following URL:
@ -73,34 +73,34 @@ Welcome!
class InvitationsPlugin(object):
def __init__(self, host):
log.info(_(u"plugin Invitations initialization"))
log.info(_("plugin Invitations initialization"))
self.host = host
self.invitations = persistent.LazyPersistentBinaryDict(u'invitations')
self.invitations = persistent.LazyPersistentBinaryDict('invitations')
host.bridge.addMethod("invitationCreate", ".plugin", in_sign='sasssssssssa{ss}s',
out_sign='a{ss}',
method=self._create,
async=True)
async_=True)
host.bridge.addMethod("invitationGet", ".plugin", in_sign='s', out_sign='a{ss}',
method=self.get,
async=True)
async_=True)
host.bridge.addMethod("invitationModify", ".plugin", in_sign='sa{ss}b',
out_sign='',
method=self._modify,
async=True)
async_=True)
host.bridge.addMethod("invitationList", ".plugin", in_sign='s',
out_sign='a{sa{ss}}',
method=self._list,
async=True)
async_=True)
def checkExtra(self, extra):
if EXTRA_RESERVED.intersection(extra):
raise ValueError(
_(u"You can't use following key(s) in extra, they are reserved: {}")
.format(u', '.join(EXTRA_RESERVED.intersection(extra))))
_("You can't use following key(s) in extra, they are reserved: {}")
.format(', '.join(EXTRA_RESERVED.intersection(extra))))
def _create(self, email=u'', emails_extra=None, jid_=u'', password=u'', name=u'',
host_name=u'', language=u'', url_template=u'', message_subject=u'',
message_body=u'', extra=None, profile=u''):
def _create(self, email='', emails_extra=None, jid_='', password='', name='',
host_name='', language='', url_template='', message_subject='',
message_body='', extra=None, profile=''):
# XXX: we don't use **kwargs here to keep arguments name for introspection with
# D-Bus bridge
if emails_extra is None:
@ -109,10 +109,10 @@ class InvitationsPlugin(object):
if extra is None:
extra = {}
else:
extra = {unicode(k): unicode(v) for k,v in extra.iteritems()}
extra = {str(k): str(v) for k,v in extra.items()}
kwargs = {"extra": extra,
KEY_EMAILS_EXTRA: [unicode(e) for e in emails_extra]
KEY_EMAILS_EXTRA: [str(e) for e in emails_extra]
}
# we need to be sure that values are unicode, else they won't be pickled correctly
@ -121,7 +121,7 @@ class InvitationsPlugin(object):
"url_template", "message_subject", "message_body", "profile"):
value = locals()[key]
if value:
kwargs[key] = unicode(value)
kwargs[key] = str(value)
d = self.create(**kwargs)
def serialize(data):
data[KEY_JID] = data[KEY_JID].full()
@ -131,7 +131,7 @@ class InvitationsPlugin(object):
@defer.inlineCallbacks
def create(self, **kwargs):
ur"""Create an invitation
r"""Create an invitation
This will create an XMPP account and a profile, and use a UUID to retrieve them.
The profile is automatically generated in the form guest@@[UUID], this way they
@ -194,29 +194,29 @@ class InvitationsPlugin(object):
extra = kwargs.pop('extra', {})
if set(kwargs).intersection(extra):
raise ValueError(
_(u"You can't use following key(s) in both args and extra: {}").format(
u', '.join(set(kwargs).intersection(extra))))
_("You can't use following key(s) in both args and extra: {}").format(
', '.join(set(kwargs).intersection(extra))))
self.checkExtra(extra)
email = kwargs.pop(u'email', None)
emails_extra = kwargs.pop(u'emails_extra', [])
email = kwargs.pop('email', None)
emails_extra = kwargs.pop('emails_extra', [])
if not email and emails_extra:
raise ValueError(
_(u'You need to provide a main email address before using emails_extra'))
_('You need to provide a main email address before using emails_extra'))
if (email is not None
and not 'url_template' in kwargs
and not 'message_body' in kwargs):
raise ValueError(
_(u"You need to provide url_template if you use default message body"))
_("You need to provide url_template if you use default message body"))
## uuid
log.info(_(u"creating an invitation"))
id_ = unicode(shortuuid.uuid())
log.info(_("creating an invitation"))
id_ = str(shortuuid.uuid())
## XMPP account creation
password = kwargs.pop(u'password', None)
password = kwargs.pop('password', None)
if password is None:
password = utils.generatePassword()
assert password
@ -228,13 +228,13 @@ class InvitationsPlugin(object):
# saved and could be used to encrypt profile password.
extra[KEY_PASSWORD] = password
jid_ = kwargs.pop(u'jid_', None)
jid_ = kwargs.pop('jid_', None)
if not jid_:
domain = self.host.memory.getConfig(None, 'xmpp_domain')
if not domain:
# TODO: fallback to profile's domain
raise ValueError(_(u"You need to specify xmpp_domain in sat.conf"))
jid_ = u"invitation-{uuid}@{domain}".format(uuid=shortuuid.uuid(),
raise ValueError(_("You need to specify xmpp_domain in sat.conf"))
jid_ = "invitation-{uuid}@{domain}".format(uuid=shortuuid.uuid(),
domain=domain)
jid_ = jid.JID(jid_)
if jid_.user:
@ -245,11 +245,11 @@ class InvitationsPlugin(object):
except error.StanzaError as e:
prefix = jid_.user
idx = 0
while e.condition == u'conflict':
while e.condition == 'conflict':
if idx >= SUFFIX_MAX:
raise exceptions.ConflictError(_(u"Can't create XMPP account"))
jid_.user = prefix + '_' + unicode(idx)
log.info(_(u"requested jid already exists, trying with {}".format(
raise exceptions.ConflictError(_("Can't create XMPP account"))
jid_.user = prefix + '_' + str(idx)
log.info(_("requested jid already exists, trying with {}".format(
jid_.full())))
try:
yield self.host.plugins['XEP-0077'].registerNewAccount(jid_,
@ -258,10 +258,10 @@ class InvitationsPlugin(object):
idx += 1
else:
break
if e.condition != u'conflict':
if e.condition != 'conflict':
raise e
log.info(_(u"account {jid_} created").format(jid_=jid_.full()))
log.info(_("account {jid_} created").format(jid_=jid_.full()))
## profile creation
@ -273,66 +273,66 @@ class InvitationsPlugin(object):
profile_key=guest_profile)
yield self.host.memory.setParam("Password", password, "Connection",
profile_key=guest_profile)
name = kwargs.pop(u'name', None)
name = kwargs.pop('name', None)
if name is not None:
extra[u'name'] = name
extra['name'] = name
try:
id_plugin = self.host.plugins[u'IDENTITY']
id_plugin = self.host.plugins['IDENTITY']
except KeyError:
pass
else:
yield self.host.connect(guest_profile, password)
guest_client = self.host.getClient(guest_profile)
yield id_plugin.setIdentity(guest_client, {u'nick': name})
yield id_plugin.setIdentity(guest_client, {'nick': name})
yield self.host.disconnect(guest_profile)
## email
language = kwargs.pop(u'language', None)
language = kwargs.pop('language', None)
if language is not None:
extra[u'language'] = language.strip()
extra['language'] = language.strip()
if email is not None:
extra[u'email'] = email
extra['email'] = email
data_format.iter2dict(KEY_EMAILS_EXTRA, extra)
url_template = kwargs.pop(u'url_template', '')
url_template = kwargs.pop('url_template', '')
format_args = {
u'uuid': id_,
u'app_name': C.APP_NAME,
u'app_url': C.APP_URL}
'uuid': id_,
'app_name': C.APP_NAME,
'app_url': C.APP_URL}
if name is None:
format_args[u'name'] = email
format_args['name'] = email
else:
format_args[u'name'] = name
format_args['name'] = name
profile = kwargs.pop(u'profile', None)
profile = kwargs.pop('profile', None)
if profile is None:
format_args[u'profile'] = u''
format_args['profile'] = ''
else:
format_args[u'profile'] = extra[u'profile'] = profile
format_args['profile'] = extra['profile'] = profile
host_name = kwargs.pop(u'host_name', None)
host_name = kwargs.pop('host_name', None)
if host_name is None:
format_args[u'host_name'] = profile or _(u"somebody")
format_args['host_name'] = profile or _("somebody")
else:
format_args[u'host_name'] = extra[u'host_name'] = host_name
format_args['host_name'] = extra['host_name'] = host_name
invite_url = url_template.format(**format_args)
format_args[u'url'] = invite_url
format_args['url'] = invite_url
yield sat_email.sendEmail(
self.host,
[email] + emails_extra,
(kwargs.pop(u'message_subject', None) or DEFAULT_SUBJECT).format(
(kwargs.pop('message_subject', None) or DEFAULT_SUBJECT).format(
**format_args),
(kwargs.pop(u'message_body', None) or DEFAULT_BODY).format(**format_args),
(kwargs.pop('message_body', None) or DEFAULT_BODY).format(**format_args),
)
## extra data saving
self.invitations[id_] = extra
if kwargs:
log.warning(_(u"Not all arguments have been consumed: {}").format(kwargs))
log.warning(_("Not all arguments have been consumed: {}").format(kwargs))
extra[KEY_ID] = id_
extra[KEY_JID] = jid_
@ -348,7 +348,7 @@ class InvitationsPlugin(object):
return self.invitations[id_]
def _modify(self, id_, new_extra, replace):
return self.modify(id_, {unicode(k): unicode(v) for k,v in new_extra.iteritems()},
return self.modify(id_, {str(k): str(v) for k,v in new_extra.items()},
replace)
def modify(self, id_, new_extra, replace=False):
@ -372,9 +372,9 @@ class InvitationsPlugin(object):
continue
else:
new_data = current_data
for k,v in new_extra.iteritems():
for k,v in new_extra.items():
if k in EXTRA_RESERVED:
log.warning(_(u"Skipping reserved key {key}".format(k)))
log.warning(_("Skipping reserved key {key}".format(k)))
continue
if v:
new_data[k] = v
@ -401,9 +401,9 @@ class InvitationsPlugin(object):
C.PROF_KEY_NONE: don't filter invitations
@return list(unicode): invitations uids
"""
invitations = yield self.invitations.items()
invitations = yield list(self.invitations.items())
if profile != C.PROF_KEY_NONE:
invitations = {id_:data for id_, data in invitations.iteritems()
if data.get(u'profile') == profile}
invitations = {id_:data for id_, data in invitations.items()
if data.get('profile') == profile}
defer.returnValue(invitations)