plugin email invitations: re-use existing invitation for a given email:
if an invitation is done on an email which has already received an invitation, it is re-used and no new XMPP account or Libervia profile is created (the name is modified though). This is currently done in an inefficient way due to `LazyPersistentBinaryDict` limitations (all invitations are loaded and checked to find the email). A more efficient check should be easier to implement with storage changes in 0.9 .
This commit is contained in:
parent
d39a80ee5f
commit
ecc6f96990
1 changed files with 107 additions and 60 deletions
|
|
@ -17,6 +17,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import shortuuid
|
||||
from typing import Optional
|
||||
from twisted.internet import defer
|
||||
from twisted.words.protocols.jabber import jid
|
||||
from twisted.words.protocols.jabber import error
|
||||
|
|
@ -131,6 +132,97 @@ class InvitationsPlugin(object):
|
|||
kwargs[key] = str(value)
|
||||
return defer.ensureDeferred(self.create(**kwargs))
|
||||
|
||||
async def getExistingInvitation(self, email: Optional[str]) -> Optional[dict]:
|
||||
"""Retrieve existing invitation with given email
|
||||
|
||||
@param email: check if any invitation exist with this email
|
||||
@return: first found invitation, or None if nothing found
|
||||
"""
|
||||
# FIXME: This method is highly inefficient, it get all invitations and check them
|
||||
# one by one, this is just a temporary way to avoid creating creating new accounts
|
||||
# for an existing email. A better way will be available with Libervia 0.9.
|
||||
# TODO: use a better way to check existing invitations
|
||||
|
||||
if email is None:
|
||||
return None
|
||||
all_invitations = await self.invitations.all()
|
||||
for id_, invitation in all_invitations.items():
|
||||
if invitation.get("email") == email:
|
||||
invitation[KEY_ID] = id_
|
||||
return invitation
|
||||
|
||||
async def _createAccountAndProfile(
|
||||
self,
|
||||
id_: str,
|
||||
kwargs: dict,
|
||||
extra: dict
|
||||
) -> None:
|
||||
"""Create XMPP account and Libervia profile for guest"""
|
||||
## XMPP account creation
|
||||
password = kwargs.pop('password', None)
|
||||
if password is None:
|
||||
password = utils.generatePassword()
|
||||
assert password
|
||||
# XXX: password is here saved in clear in database
|
||||
# it is needed for invitation as the same password is used for profile
|
||||
# and SàT need to be able to automatically open the profile with the uuid
|
||||
# FIXME: we could add an extra encryption key which would be used with the
|
||||
# uuid when the invitee is connecting (e.g. with URL). This key would
|
||||
# not be saved and could be used to encrypt profile password.
|
||||
extra[KEY_PASSWORD] = password
|
||||
|
||||
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(_("You need to specify xmpp_domain in sat.conf"))
|
||||
jid_ = "invitation-{uuid}@{domain}".format(uuid=shortuuid.uuid(),
|
||||
domain=domain)
|
||||
jid_ = jid.JID(jid_)
|
||||
extra[KEY_JID] = jid_.full()
|
||||
|
||||
if jid_.user:
|
||||
# we don't register account if there is no user as anonymous login is then
|
||||
# used
|
||||
try:
|
||||
await self.host.plugins['XEP-0077'].registerNewAccount(jid_, password)
|
||||
except error.StanzaError as e:
|
||||
prefix = jid_.user
|
||||
idx = 0
|
||||
while e.condition == 'conflict':
|
||||
if idx >= SUFFIX_MAX:
|
||||
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:
|
||||
await self.host.plugins['XEP-0077'].registerNewAccount(
|
||||
jid_,
|
||||
password
|
||||
)
|
||||
except error.StanzaError:
|
||||
idx += 1
|
||||
else:
|
||||
break
|
||||
if e.condition != 'conflict':
|
||||
raise e
|
||||
|
||||
log.info(_("account {jid_} created").format(jid_=jid_.full()))
|
||||
|
||||
## profile creation
|
||||
|
||||
extra[KEY_GUEST_PROFILE] = guest_profile = INVITEE_PROFILE_TPL.format(
|
||||
uuid=id_
|
||||
)
|
||||
# profile creation should not fail as we generate unique name ourselves
|
||||
await self.host.memory.createProfile(guest_profile, password)
|
||||
await self.host.memory.startSession(password, guest_profile)
|
||||
await self.host.memory.setParam("JabberID", jid_.full(), "Connection",
|
||||
profile_key=guest_profile)
|
||||
await self.host.memory.setParam("Password", password, "Connection",
|
||||
profile_key=guest_profile)
|
||||
|
||||
async def create(self, **kwargs):
|
||||
r"""Create an invitation
|
||||
|
||||
|
|
@ -201,6 +293,13 @@ class InvitationsPlugin(object):
|
|||
self.checkExtra(extra)
|
||||
|
||||
email = kwargs.pop('email', None)
|
||||
|
||||
existing = await self.getExistingInvitation(email)
|
||||
if existing is not None:
|
||||
log.info(f"There is already an invitation for {email!r}")
|
||||
extra.update(existing)
|
||||
del extra[KEY_ID]
|
||||
|
||||
emails_extra = kwargs.pop('emails_extra', [])
|
||||
if not email and emails_extra:
|
||||
raise ValueError(
|
||||
|
|
@ -214,67 +313,18 @@ class InvitationsPlugin(object):
|
|||
|
||||
## uuid
|
||||
log.info(_("creating an invitation"))
|
||||
id_ = str(shortuuid.uuid())
|
||||
id_ = existing[KEY_ID] if existing else str(shortuuid.uuid())
|
||||
|
||||
## XMPP account creation
|
||||
password = kwargs.pop('password', None)
|
||||
if password is None:
|
||||
password = utils.generatePassword()
|
||||
assert password
|
||||
# XXX: password is here saved in clear in database
|
||||
# it is needed for invitation as the same password is used for profile
|
||||
# and SàT need to be able to automatically open the profile with the uuid
|
||||
# FIXME: we could add an extra encryption key which would be used with the uuid
|
||||
# when the invitee is connecting (e.g. with URL). This key would not be
|
||||
# saved and could be used to encrypt profile password.
|
||||
extra[KEY_PASSWORD] = password
|
||||
if existing is None:
|
||||
await self._createAccountAndProfile(id_, kwargs, extra)
|
||||
|
||||
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(_("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:
|
||||
# we don't register account if there is no user as anonymous login is then
|
||||
# used
|
||||
try:
|
||||
await self.host.plugins['XEP-0077'].registerNewAccount(jid_, password)
|
||||
except error.StanzaError as e:
|
||||
prefix = jid_.user
|
||||
idx = 0
|
||||
while e.condition == 'conflict':
|
||||
if idx >= SUFFIX_MAX:
|
||||
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:
|
||||
await self.host.plugins['XEP-0077'].registerNewAccount(jid_,
|
||||
password)
|
||||
except error.StanzaError:
|
||||
idx += 1
|
||||
else:
|
||||
break
|
||||
if e.condition != 'conflict':
|
||||
raise e
|
||||
profile = kwargs.pop('profile', None)
|
||||
guest_profile = extra[KEY_GUEST_PROFILE]
|
||||
jid_ = jid.JID(extra[KEY_JID])
|
||||
|
||||
log.info(_("account {jid_} created").format(jid_=jid_.full()))
|
||||
|
||||
## profile creation
|
||||
|
||||
extra[KEY_GUEST_PROFILE] = guest_profile = INVITEE_PROFILE_TPL.format(uuid=id_)
|
||||
# profile creation should not fail as we generate unique name ourselves
|
||||
await self.host.memory.createProfile(guest_profile, password)
|
||||
await self.host.memory.startSession(password, guest_profile)
|
||||
await self.host.memory.setParam("JabberID", jid_.full(), "Connection",
|
||||
profile_key=guest_profile)
|
||||
await self.host.memory.setParam("Password", password, "Connection",
|
||||
profile_key=guest_profile)
|
||||
## identity
|
||||
name = kwargs.pop('name', None)
|
||||
password = extra[KEY_PASSWORD]
|
||||
if name is not None:
|
||||
extra['name'] = name
|
||||
try:
|
||||
|
|
@ -306,7 +356,6 @@ class InvitationsPlugin(object):
|
|||
else:
|
||||
format_args['name'] = name
|
||||
|
||||
profile = kwargs.pop('profile', None)
|
||||
if profile is None:
|
||||
format_args['profile'] = ''
|
||||
else:
|
||||
|
|
@ -344,8 +393,6 @@ class InvitationsPlugin(object):
|
|||
if kwargs:
|
||||
log.warning(_("Not all arguments have been consumed: {}").format(kwargs))
|
||||
|
||||
extra[KEY_JID] = jid_.full()
|
||||
|
||||
## extra data saving
|
||||
self.invitations[id_] = extra
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue