core (memory): file metadata handling methods:
- checkFilePermission check that requested permission(s) are granted for a file (without checking the parents) - checkPermissionToRoot check that requested permission(s) are granted for a file and all its parents - getFiles retrieve file(s) according to filter, and check permissions by default - setFile create a file (and its parents if needed) and check permissions by default
This commit is contained in:
parent
1301132bed
commit
db45d45be5
1 changed files with 242 additions and 0 deletions
|
|
@ -39,6 +39,9 @@ from sat.memory.disco import Discovery
|
|||
from sat.memory.crypto import BlockCipher
|
||||
from sat.memory.crypto import PasswordHasher
|
||||
from sat.tools import config as tools_config
|
||||
import shortuuid
|
||||
import mimetypes
|
||||
import time
|
||||
|
||||
|
||||
PresenceTuple = namedtuple("PresenceTuple", ('show', 'priority', 'statuses'))
|
||||
|
|
@ -1058,6 +1061,245 @@ class Memory(object):
|
|||
def setDefault(self, name, category, callback, errback=None):
|
||||
return self.params.setDefault(name, category, callback, errback)
|
||||
|
||||
## Files ##
|
||||
|
||||
def checkFilePermission(self, file_data, peer_jid, perms_to_check):
|
||||
"""check that an entity has the right permission on a file
|
||||
|
||||
@param file_data(dict): data of one file, as returned by getFiles
|
||||
@param peer_jid(jid.JID): entity trying to access the file
|
||||
@param perms_to_check(tuple[unicode]): permissions to check
|
||||
tuple of C.ACCESS_PERM_*
|
||||
@param check_parents(bool): if True, also check all parents until root node
|
||||
@raise exceptions.PermissionError: peer_jid doesn't have all permission
|
||||
in perms_to_check for file_data
|
||||
@raise exceptions.InternalError: perms_to_check is invalid
|
||||
"""
|
||||
if peer_jid is None and perms_to_check is None:
|
||||
return
|
||||
peer_jid = peer_jid.userhostJID()
|
||||
if peer_jid == file_data['owner']:
|
||||
# the owner has all rights
|
||||
return
|
||||
if not C.ACCESS_PERMS.issuperset(perms_to_check):
|
||||
raise exceptions.InternalError(_(u'invalid permission'))
|
||||
|
||||
for perm in perms_to_check:
|
||||
# we check each perm and raise PermissionError as soon as one condition is not valid
|
||||
# we must never return here, we only return after the loop if nothing was blocking the access
|
||||
try:
|
||||
perm_data = file_data[u'access'][perm]
|
||||
perm_type = perm_data[u'type']
|
||||
except KeyError:
|
||||
raise failure.Failure(exceptions.PermissionError())
|
||||
if perm_type == C.ACCESS_TYPE_PUBLIC:
|
||||
continue
|
||||
elif perm_type == C.ACCESS_TYPE_WHITELIST:
|
||||
try:
|
||||
jids = perm_data[u'jids']
|
||||
except KeyError:
|
||||
raise failure.Failure(exceptions.PermissionError())
|
||||
if peer_jid.full() in jids:
|
||||
continue
|
||||
else:
|
||||
raise failure.Failure(exceptions.PermissionError())
|
||||
else:
|
||||
raise exceptions.InternalError(_(u'unknown access type: {type}').format(type=perm_type))
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def checkPermissionToRoot(self, client, file_data, peer_jid, perms_to_check):
|
||||
"""do checkFilePermission on file_data and all its parents until root"""
|
||||
current = file_data
|
||||
while True:
|
||||
self.checkFilePermission(current, peer_jid, perms_to_check)
|
||||
parent = current[u'parent']
|
||||
if not parent:
|
||||
break
|
||||
files_data = yield self.getFile(self, client, peer_jid=None, file_id=parent, perms_to_check=None)
|
||||
try:
|
||||
current = files_data[0]
|
||||
except IndexError:
|
||||
raise exceptions.DataError(u'Missing parent')
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _getParentDir(self, client, path, parent, namespace, owner, peer_jid, perms_to_check):
|
||||
"""Retrieve parent node from a path, or last existing directory
|
||||
|
||||
each directory of the path will be retrieved, until the last existing one
|
||||
@return (tuple[unicode, list[unicode])): parent, remaining path elements:
|
||||
- parent is the id of the last retrieved directory (or u'' for root)
|
||||
- remaining path elements are the directories which have not been retrieved
|
||||
(i.e. which don't exist)
|
||||
"""
|
||||
# if path is set, we have to retrieve parent directory of the file(s) from it
|
||||
if parent is not None:
|
||||
raise exceptions.ConflictError(_(u"You can't use path and parent at the same time"))
|
||||
path_elts = filter(None, path.split(u'/'))
|
||||
if {u'..', u'.'}.intersection(path_elts):
|
||||
raise ValueError(_(u'".." or "." can\'t be used in path'))
|
||||
|
||||
# we retrieve all directories from path until we get the parent container
|
||||
# non existing directories will be created
|
||||
parent = u''
|
||||
for idx, path_elt in enumerate(path_elts):
|
||||
directories = yield self.storage.getFiles(client, parent=parent, type_=C.FILE_TYPE_DIRECTORY,
|
||||
name=path_elt, namespace=namespace, owner=owner)
|
||||
if not directories:
|
||||
defer.returnValue((parent, path_elts[idx:]))
|
||||
# from this point, directories don't exist anymore, we have to create them
|
||||
elif len(directories) > 1:
|
||||
raise exceptions.InternalError(_(u"Several directories found, this should not happen"))
|
||||
else:
|
||||
directory = directories[0]
|
||||
self.checkFilePermission(directory, peer_jid, perms_to_check)
|
||||
parent = directory[u'id']
|
||||
defer.returnValue((parent, []))
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def getFiles(self, client, peer_jid, file_id=None, version=None, parent=None, path=None, type_=None,
|
||||
file_hash=None, hash_algo=None, name=None, namespace=None, mime_type=None,
|
||||
owner=None, access=None, projection=None, unique=False, perms_to_check=(C.ACCESS_PERM_READ,)):
|
||||
"""retrieve files with with given filters
|
||||
|
||||
@param peer_jid(jid.JID, None): jid trying to access the file
|
||||
needed to check permission.
|
||||
Use None to ignore permission (perms_to_check must be None too)
|
||||
@param file_id(unicode, None): id of the file
|
||||
None to ignore
|
||||
@param version(unicode, None): version of the file
|
||||
None to ignore
|
||||
empty string to look for current version
|
||||
@param parent(unicode, None): id of the directory containing the files
|
||||
None to ignore
|
||||
empty string to look for root files/directories
|
||||
@param projection(list[unicode], None): name of columns to retrieve
|
||||
None to retrieve all
|
||||
@param unique(bool): if True will remove duplicates
|
||||
@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)
|
||||
other params are the same as for [setFile]
|
||||
@return (list[dict]): files corresponding to filters
|
||||
@raise exceptions.NotFound: parent directory not found (when path is specified)
|
||||
@raise exceptions.PermissionError: peer_jid can't use perms_to_check for one of the file
|
||||
on the path
|
||||
"""
|
||||
if peer_jid is None and perms_to_check or perms_to_check is None and peer_jid:
|
||||
raise exceptions.InternalError('if you want to disable permission check, both peer_jid and perms_to_check must be None')
|
||||
if path is not None:
|
||||
# permission are checked by _getParentDir
|
||||
parent, remaining_path_elts = yield self._getParentDir(client, path, parent, namespace, owner, peer_jid, perms_to_check)
|
||||
if remaining_path_elts:
|
||||
# if we have remaining path elements,
|
||||
# the parent directory is not found
|
||||
raise failure.Failure(exceptions.NotFound())
|
||||
if parent and peer_jid:
|
||||
# if parent is given directly and permission check is need,
|
||||
# we need to check all the parents
|
||||
parent_data = yield self.storage.getFiles(client, file_id=parent)
|
||||
try:
|
||||
parent_data = parent_data[0]
|
||||
except IndexError:
|
||||
raise exceptions.DataError(u'mising parent')
|
||||
yield self.checkPermissionToRoot(client, parent_data, peer_jid, perms_to_check)
|
||||
|
||||
files = yield self.storage.getFiles(client, file_id=file_id, version=version, parent=parent, type_=type_,
|
||||
file_hash=file_hash, hash_algo=hash_algo, name=name, namespace=namespace,
|
||||
mime_type=mime_type, owner=owner, access=access,
|
||||
projection=projection, unique=unique)
|
||||
|
||||
if peer_jid:
|
||||
# if permission are checked, we must remove all file tha use can't access
|
||||
to_remove = []
|
||||
for file_data in files:
|
||||
try:
|
||||
self.checkFilePermission(file_data, peer_jid, perms_to_check)
|
||||
except exceptions.PermissionError:
|
||||
to_remove.append(file_data)
|
||||
for file_data in to_remove:
|
||||
files.remove(file_data)
|
||||
defer.returnValue(files)
|
||||
|
||||
@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,)):
|
||||
"""set a file metadata
|
||||
|
||||
@param name(unicode): basename of the file
|
||||
@param file_id(unicode): unique id of the file
|
||||
@param version(unicode): version of this file
|
||||
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.
|
||||
@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 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)
|
||||
will be used to check permission.
|
||||
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
|
||||
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
|
||||
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)
|
||||
@param profile(unicode): profile owning the file
|
||||
"""
|
||||
if '/' in name:
|
||||
raise ValueError('name must not contain a slash ("/")')
|
||||
if file_id is None:
|
||||
file_id = shortuuid.uuid()
|
||||
if file_hash is not None and hash_algo is None or hash_algo is not None and file_hash is None:
|
||||
raise ValueError('file_hash and hash_algo must be set at the same time')
|
||||
if mime_type is None:
|
||||
mime_type, file_encoding = mimetypes.guess_type(name)
|
||||
if created is None:
|
||||
created = time.time()
|
||||
if namespace is not None:
|
||||
namespace = namespace.strip() or None
|
||||
if type_ == C.FILE_TYPE_DIRECTORY:
|
||||
if any(version, file_hash, size, mime_type):
|
||||
raise ValueError(u"version, file_hash, size and mime_type can't be set for a directory")
|
||||
|
||||
if path is not None:
|
||||
if peer_jid is None:
|
||||
peer_jid = owner
|
||||
# _getParentDir will check permissions if peer_jid is set
|
||||
parent, remaining_path_elts = self._getParentDir(client, path, parent, namespace, owner, owner, perms_to_check)
|
||||
# if remaining directories don't exist, we have to create them
|
||||
for new_dir in remaining_path_elts:
|
||||
new_dir_id = shortuuid.uuid()
|
||||
yield self.storage.setFile(client, name=new_dir, file_id=new_dir_id, version=u'', parent=parent,
|
||||
type_=C.FILE_TYPE_DIRECTORY, namespace=namespace,
|
||||
created=time.time(),
|
||||
owner=owner.userhostJID() if owner else None,
|
||||
access=access, extra={})
|
||||
parent = new_dir_id
|
||||
elif parent is None:
|
||||
parent = u''
|
||||
|
||||
yield self.storage.setFile(client, file_id=file_id, version=version, parent=parent, type_=type_,
|
||||
file_hash=file_hash, hash_algo=hash_algo, name=name, size=size,
|
||||
namespace=namespace, mime_type=mime_type, created=created, modified=modified,
|
||||
owner=owner.userhostJID() if owner else None,
|
||||
access=access, extra=extra)
|
||||
|
||||
## Misc ##
|
||||
|
||||
def isEntityAvailable(self, entity_jid, profile_key):
|
||||
|
|
|
|||
Loading…
Reference in a new issue