changeset 2909:90146552cde5

core (memory), plugin XEP-0329, plugin invitation: minor style improvments
author Goffi <goffi@goffi.org>
date Sun, 14 Apr 2019 08:21:51 +0200
parents 695fc440c3b8
children b2f323237fce
files sat/memory/memory.py sat/plugins/plugin_misc_invitations.py sat/plugins/plugin_xep_0329.py
diffstat 3 files changed, 124 insertions(+), 96 deletions(-) [+]
line wrap: on
line diff
--- a/sat/memory/memory.py	Sun Apr 14 08:21:51 2019 +0200
+++ b/sat/memory/memory.py	Sun Apr 14 08:21:51 2019 +0200
@@ -1417,27 +1417,11 @@
 
     @defer.inlineCallbacks
     def setFile(
-        self,
-        client,
-        name,
-        file_id=None,
-        version=u"",
-        parent=None,
-        path=None,
-        type_=C.FILE_TYPE_FILE,
-        file_hash=None,
-        hash_algo=None,
-        size=None,
-        namespace=None,
-        mime_type=None,
-        created=None,
-        modified=None,
-        owner=None,
-        access=None,
-        extra=None,
-        peer_jid=None,
-        perms_to_check=(C.ACCESS_PERM_WRITE,),
-    ):
+            self, client, name, file_id=None, version=u"", parent=None, path=None,
+            type_=C.FILE_TYPE_FILE, file_hash=None, hash_algo=None, size=None,
+            namespace=None, mime_type=None, created=None, modified=None, owner=None,
+            access=None, extra=None, peer_jid=None, perms_to_check=(C.ACCESS_PERM_WRITE,)
+            ):
         """set a file metadata
 
         @param name(unicode): basename of the file
@@ -1446,32 +1430,38 @@
             empty string for current version or when there is no versioning
         @param parent(unicode, None): id of the directory containing the files
         @param path(unicode, None): virtual path of the file in the namespace
-            if set, parent must be None. All intermediate directories will be created if needed,
-            using current access.
+            if set, parent must be None. All intermediate directories will be created
+            if needed, using current access.
         @param file_hash(unicode): unique hash of the payload
         @param hash_algo(unicode): algorithm used for hashing the file (usually sha-256)
         @param size(int): size in bytes
-        @param namespace(unicode, None): identifier (human readable is better) to group files
-            for instance, namespace could be used to group files in a specific photo album
+        @param namespace(unicode, None): identifier (human readable is better) to group
+                                         files
+            For instance, namespace could be used to group files in a specific photo album
         @param mime_type(unicode): MIME type of the file, or None if not known/guessed
         @param created(int): UNIX time of creation
-        @param modified(int,None): UNIX time of last modification, or None to use created date
-        @param owner(jid.JID, None): jid of the owner of the file (mainly useful for component)
+        @param modified(int,None): UNIX time of last modification, or None to use
+                                   created date
+        @param owner(jid.JID, None): jid of the owner of the file (mainly useful for
+                                     component)
             will be used to check permission (only bare jid is used, don't use with MUC).
             Use None to ignore permission (perms_to_check must be None too)
         @param access(dict, None): serialisable dictionary with access rules.
-            None (or empty dict) to use private access, i.e. allow only profile's jid to access the file
+            None (or empty dict) to use private access, i.e. allow only profile's jid to
+            access the file
             key can be on on C.ACCESS_PERM_*,
             then a sub dictionary with a type key is used (one of C.ACCESS_TYPE_*).
             According to type, extra keys can be used:
                 - C.ACCESS_TYPE_PUBLIC: the permission is granted for everybody
-                - C.ACCESS_TYPE_WHITELIST: the permission is granted for jids (as unicode) in the 'jids' key
+                - C.ACCESS_TYPE_WHITELIST: the permission is granted for jids (as unicode)
+                  in the 'jids' key
             will be encoded to json in database
         @param extra(dict, None): serialisable dictionary of any extra data
             will be encoded to json in database
         @param perms_to_check(tuple[unicode],None): permission to check
             must be a tuple of C.ACCESS_PERM_* or None
-            if None, permission will no be checked (peer_jid must be None too in this case)
+            if None, permission will no be checked (peer_jid must be None too in this
+            case)
         @param profile(unicode): profile owning the file
         """
         if "/" in name:
--- a/sat/plugins/plugin_misc_invitations.py	Sun Apr 14 08:21:51 2019 +0200
+++ b/sat/plugins/plugin_misc_invitations.py	Sun Apr 14 08:21:51 2019 +0200
@@ -17,20 +17,21 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import shortuuid
+from twisted.internet import defer
+from twisted.words.protocols.jabber import jid
+from twisted.words.protocols.jabber import error
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core import exceptions
 from sat.core.log import getLogger
-log = getLogger(__name__)
-import shortuuid
 from sat.tools import utils
 from sat.tools.common import data_format
-from twisted.internet import defer
-from twisted.words.protocols.jabber import jid
-from twisted.words.protocols.jabber import error
 from sat.memory import persistent
 from sat.tools import email as sat_email
 
+log = getLogger(__name__)
+
 
 PLUGIN_INFO = {
     C.PI_NAME: "Invitations",
@@ -53,7 +54,8 @@
 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, KEY_GUEST_PROFILE, KEY_PASSWORD, KEY_EMAILS_EXTRA}
+EXTRA_RESERVED = {KEY_ID, KEY_JID, KEY_CREATED, u'jid_', u'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}!
 
@@ -74,26 +76,33 @@
         log.info(_(u"plugin Invitations initialization"))
         self.host = host
         self.invitations = persistent.LazyPersistentBinaryDict(u'invitations')
-        host.bridge.addMethod("invitationCreate", ".plugin", in_sign='sasssssssssa{ss}s', out_sign='a{ss}',
+        host.bridge.addMethod("invitationCreate", ".plugin", in_sign='sasssssssssa{ss}s',
+                              out_sign='a{ss}',
                               method=self._create,
                               async=True)
         host.bridge.addMethod("invitationGet", ".plugin", in_sign='s', out_sign='a{ss}',
                               method=self.get,
                               async=True)
-        host.bridge.addMethod("invitationModify", ".plugin", in_sign='sa{ss}b', out_sign='',
+        host.bridge.addMethod("invitationModify", ".plugin", in_sign='sa{ss}b',
+                              out_sign='',
                               method=self._modify,
                               async=True)
-        host.bridge.addMethod("invitationList", ".plugin", in_sign='s', out_sign='a{sa{ss}}',
+        host.bridge.addMethod("invitationList", ".plugin", in_sign='s',
+                              out_sign='a{sa{ss}}',
                               method=self._list,
                               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))))
+            raise ValueError(
+                _(u"You can't use following key(s) in extra, they are reserved: {}")
+                .format(u', '.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''):
-        # XXX: we don't use **kwargs here to keep arguments name for introspection with D-Bus bridge
+    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''):
+        # XXX: we don't use **kwargs here to keep arguments name for introspection with
+        #      D-Bus bridge
         if emails_extra is None:
             emails_extra = []
 
@@ -106,8 +115,10 @@
                   KEY_EMAILS_EXTRA: [unicode(e) for e in emails_extra]
                   }
 
-        # we need to be sure that values are unicode, else they won't be pickled correctly with D-Bus
-        for key in ("jid_", "password", "name", "host_name", "email", "language", "url_template", "message_subject", "message_body", "profile"):
+        # we need to be sure that values are unicode, else they won't be pickled correctly
+        # with D-Bus
+        for key in ("jid_", "password", "name", "host_name", "email", "language",
+                    "url_template", "message_subject", "message_body", "profile"):
             value = locals()[key]
             if value:
                 kwargs[key] = unicode(value)
@@ -120,32 +131,45 @@
 
     @defer.inlineCallbacks
     def create(self, **kwargs):
-        ur"""create an invitation
+        ur"""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 can be retrieved easily
-        **kwargs: keywords arguments which can have the following keys, unset values are equivalent to None:
-            jid_(jid.JID, None): jid to use for invitation, the jid will be created using XEP-0077
-                if the jid has no user part, an anonymous account will be used (no XMPP account created in this case)
-                if None, automatically generate an account name (in the form "invitation-[random UUID]@domain.tld") (note that this UUID is not the
-                    same as the invitation one, as jid can be used publicly (leaking the UUID), and invitation UUID give access to account.
-                in case of conflict, a suffix number is added to the account until a free one if found (with a failure if SUFFIX_MAX is reached)
-            password(unicode, None): password to use (will be used for XMPP account and profile)
+        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
+            can be retrieved easily
+        **kwargs: keywords arguments which can have the following keys, unset values are
+                  equivalent to None:
+            jid_(jid.JID, None): jid to use for invitation, the jid will be created using
+                                 XEP-0077
+                if the jid has no user part, an anonymous account will be used (no XMPP
+                    account created in this case)
+                if None, automatically generate an account name (in the form
+                    "invitation-[random UUID]@domain.tld") (note that this UUID is not the
+                    same as the invitation one, as jid can be used publicly (leaking the
+                    UUID), and invitation UUID give access to account.
+                in case of conflict, a suffix number is added to the account until a free
+                    one if found (with a failure if SUFFIX_MAX is reached)
+            password(unicode, None): password to use (will be used for XMPP account and
+                                     profile)
                 None to automatically generate one
             name(unicode, None): name of the invitee
                 will be set as profile identity if present
             host_name(unicode, None): name of the host
             email(unicode, None): email to send the invitation to
-                if None, no invitation email is sent, you can still associate email using extra
+                if None, no invitation email is sent, you can still associate email using
+                    extra
                 if email is used, extra can't have "email" key
-            language(unicode): language of the invitee (used notabily to translate the invitation)
+            language(unicode): language of the invitee (used notabily to translate the
+                               invitation)
                 TODO: not used yet
             url_template(unicode, None): template to use to construct the invitation URL
                 use {uuid} as a placeholder for identifier
-                use None if you don't want to include URL (or if it is already specified in custom message)
+                use None if you don't want to include URL (or if it is already specified
+                    in custom message)
                 /!\ you must put full URL, don't forget https://
-                /!\ the URL will give access to the invitee account, you should warn in message to not publish it publicly
-            message_subject(unicode, None): customised message body for the invitation email
+                /!\ the URL will give access to the invitee account, you should warn in
+                    message to not publish it publicly
+            message_subject(unicode, None): customised message body for the invitation
+                                            email
                 None to use default subject
                 uses the same substitution as for message_body
             message_body(unicode, None): customised message body for the invitation email
@@ -169,7 +193,8 @@
         ## initial checks
         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(
+            raise ValueError(
+                _(u"You can't use following key(s) in both args and extra: {}").format(
                 u', '.join(set(kwargs).intersection(extra))))
 
         self.checkExtra(extra)
@@ -177,10 +202,14 @@
         email = kwargs.pop(u'email', None)
         emails_extra = kwargs.pop(u'emails_extra', [])
         if not email and emails_extra:
-            raise ValueError(_(u'You need to provide a main email address before using emails_extra'))
+            raise ValueError(
+                _(u'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"))
+        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"))
 
         ## uuid
         log.info(_(u"creating an invitation"))
@@ -195,8 +224,8 @@
         #      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.
+        #        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(u'jid_', None)
@@ -205,10 +234,12 @@
             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(), domain=domain)
+            jid_ = u"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
+            # we don't register account if there is no user as anonymous login is then
+            # used
             try:
                 yield self.host.plugins['XEP-0077'].registerNewAccount(jid_, password)
             except error.StanzaError as e:
@@ -218,9 +249,11 @@
                     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(jid_.full())))
+                    log.info(_(u"requested jid already exists, trying with {}".format(
+                        jid_.full())))
                     try:
-                        yield self.host.plugins['XEP-0077'].registerNewAccount(jid_, password)
+                        yield self.host.plugins['XEP-0077'].registerNewAccount(jid_,
+                                                                               password)
                     except error.StanzaError as e:
                         idx += 1
                     else:
@@ -236,8 +269,10 @@
         # profile creation should not fail as we generate unique name ourselves
         yield self.host.memory.createProfile(guest_profile, password)
         yield self.host.memory.startSession(password, guest_profile)
-        yield self.host.memory.setParam("JabberID", jid_.full(), "Connection", profile_key=guest_profile)
-        yield self.host.memory.setParam("Password", password, "Connection", profile_key=guest_profile)
+        yield self.host.memory.setParam("JabberID", jid_.full(), "Connection",
+                                        profile_key=guest_profile)
+        yield self.host.memory.setParam("Password", password, "Connection",
+                                        profile_key=guest_profile)
         name = kwargs.pop(u'name', None)
         if name is not None:
             extra[u'name'] = name
@@ -288,7 +323,8 @@
             yield sat_email.sendEmail(
                 self.host,
                 [email] + emails_extra,
-                (kwargs.pop(u'message_subject', None) or DEFAULT_SUBJECT).format(**format_args),
+                (kwargs.pop(u'message_subject', None) or DEFAULT_SUBJECT).format(
+                    **format_args),
                 (kwargs.pop(u'message_body', None) or DEFAULT_BODY).format(**format_args),
             )
 
@@ -312,7 +348,8 @@
         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()}, replace)
+        return self.modify(id_, {unicode(k): unicode(v) for k,v in new_extra.iteritems()},
+                           replace)
 
     def modify(self, id_, new_extra, replace=False):
         """Modify invitation data
@@ -366,6 +403,7 @@
         """
         invitations = yield 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.iteritems()
+                           if data.get(u'profile') == profile}
 
         defer.returnValue(invitations)
--- a/sat/plugins/plugin_xep_0329.py	Sun Apr 14 08:21:51 2019 +0200
+++ b/sat/plugins/plugin_xep_0329.py	Sun Apr 14 08:21:51 2019 +0200
@@ -59,7 +59,7 @@
 
 
 class ShareNode(object):
-    """node containing directory or files to share, virtual or real"""
+    """Node containing directory or files to share, virtual or real"""
 
     host = None
 
@@ -112,7 +112,7 @@
         return self.children.itervalues()
 
     def getOrCreate(self, name, type_=TYPE_VIRTUAL, access=None):
-        """get a node or create a virtual node and return it"""
+        """Get a node or create a virtual node and return it"""
         if access is None:
             access = {C.ACCESS_PERM_READ: {KEY_TYPE: C.ACCESS_TYPE_PUBLIC}}
         try:
@@ -157,11 +157,12 @@
     def checkPermissions(
         self, client, peer_jid, perms=(C.ACCESS_PERM_READ,), check_parents=True
     ):
-        """check that peer_jid can access this node and all its parents
+        """Check that peer_jid can access this node and all its parents
 
         @param peer_jid(jid.JID): entrity trying to access the node
         @param perms(unicode): permissions to check, iterable of C.ACCESS_PERM_*
-        @param check_parents(bool): if True, access of all parents of this node will be checked too
+        @param check_parents(bool): if True, access of all parents of this node will be
+            checked too
         @return (bool): True if entity can access this node
         """
         peer_jid = peer_jid.userhostJID()
@@ -191,11 +192,9 @@
         path_elts = filter(None, path.split(u"/"))
 
         if u".." in path_elts:
-            log.warning(
-                _(
-                    u'parent dir ("..") found in path, hack attempt? path is {path} [{profile}]'
-                ).format(path=path, profile=client.profile)
-            )
+            log.warning(_(
+                u'parent dir ("..") found in path, hack attempt? path is {path} '
+                u'[{profile}]').format(path=path, profile=client.profile))
             raise exceptions.PermissionError(u"illegal path elements")
 
         if not path_elts:
@@ -312,9 +311,10 @@
     def _fileSendingRequestTrigger(
         self, client, session, content_data, content_name, file_data, file_elt
     ):
-        """this trigger check that a requested file is available, and fill suitable data if so
+        """This trigger check that a requested file is available, and fill suitable data
 
-        path and name are used to retrieve the file. If path is missing, we try our luck with known names
+        Path and name are used to retrieve the file. If path is missing, we try our luck
+        with known names
         """
         if client.is_component:
             return True, None
@@ -423,7 +423,8 @@
             )
 
             query_elt.addChild(file_elt)
-            # we don't specify hash as it would be too resource intensive to calculate it for all files
+            # we don't specify hash as it would be too resource intensive to calculate
+            # it for all files.
             # we add file to name_data, so users can request it later
             name_data = client._XEP_0329_names_data.setdefault(name, {})
             if path not in name_data:
@@ -547,7 +548,9 @@
         """retrieve files from local files repository according to permissions
 
         result stanza is then built and sent to requestor
-        @trigger XEP-0329_compGetFilesFromNode(client, iq_elt, owner, node_path, files_data): can be used to add data/elements
+        @trigger XEP-0329_compGetFilesFromNode(client, iq_elt, owner, node_path,
+                                               files_data):
+            can be used to add data/elements
         """
         peer_jid = jid.JID(iq_elt["from"])
         try:
@@ -660,7 +663,8 @@
         node = client._XEP_0329_root_node
         node_type = TYPE_PATH
         if os.path.isfile(path):
-            #  we have a single file, the workflow is diferrent as we store all single files in the same dir
+            # we have a single file, the workflow is diferrent as we store all single
+            # files in the same dir
             node = node.getOrCreate(SINGLE_FILES_DIR)
 
         if not name:
@@ -675,13 +679,9 @@
                 idx += 1
                 new_name = name + "_" + unicode(idx)
             name = new_name
-            log.info(
-                _(
-                    u"A directory with this name is already shared, renamed to {new_name} [{profile}]".format(
-                        new_name=new_name, profile=client.profile
-                    )
-                )
-            )
+            log.info(_(
+                u"A directory with this name is already shared, renamed to {new_name} "
+                u"[{profile}]".format( new_name=new_name, profile=client.profile)))
 
         ShareNode(name=name, parent=node, type_=node_type, access=access, path=path)
         self.host.bridge.FISSharedPathNew(path, name, client.profile)