Mercurial > libervia-backend
comparison src/memory/memory.py @ 2501:3b67fe672206
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
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 28 Feb 2018 18:28:39 +0100 |
parents | 0046283a285d |
children | 4e5cc45e2be7 |
comparison
equal
deleted
inserted
replaced
2500:898b6e1fdc7a | 2501:3b67fe672206 |
---|---|
37 from sat.memory.params import Params | 37 from sat.memory.params import Params |
38 from sat.memory.disco import Discovery | 38 from sat.memory.disco import Discovery |
39 from sat.memory.crypto import BlockCipher | 39 from sat.memory.crypto import BlockCipher |
40 from sat.memory.crypto import PasswordHasher | 40 from sat.memory.crypto import PasswordHasher |
41 from sat.tools import config as tools_config | 41 from sat.tools import config as tools_config |
42 import shortuuid | |
43 import mimetypes | |
44 import time | |
42 | 45 |
43 | 46 |
44 PresenceTuple = namedtuple("PresenceTuple", ('show', 'priority', 'statuses')) | 47 PresenceTuple = namedtuple("PresenceTuple", ('show', 'priority', 'statuses')) |
45 MSG_NO_SESSION = "Session id doesn't exist or is finished" | 48 MSG_NO_SESSION = "Session id doesn't exist or is finished" |
46 | 49 |
1056 return self.params.paramsRegisterApp(xml, security_limit, app) | 1059 return self.params.paramsRegisterApp(xml, security_limit, app) |
1057 | 1060 |
1058 def setDefault(self, name, category, callback, errback=None): | 1061 def setDefault(self, name, category, callback, errback=None): |
1059 return self.params.setDefault(name, category, callback, errback) | 1062 return self.params.setDefault(name, category, callback, errback) |
1060 | 1063 |
1064 ## Files ## | |
1065 | |
1066 def checkFilePermission(self, file_data, peer_jid, perms_to_check): | |
1067 """check that an entity has the right permission on a file | |
1068 | |
1069 @param file_data(dict): data of one file, as returned by getFiles | |
1070 @param peer_jid(jid.JID): entity trying to access the file | |
1071 @param perms_to_check(tuple[unicode]): permissions to check | |
1072 tuple of C.ACCESS_PERM_* | |
1073 @param check_parents(bool): if True, also check all parents until root node | |
1074 @raise exceptions.PermissionError: peer_jid doesn't have all permission | |
1075 in perms_to_check for file_data | |
1076 @raise exceptions.InternalError: perms_to_check is invalid | |
1077 """ | |
1078 if peer_jid is None and perms_to_check is None: | |
1079 return | |
1080 peer_jid = peer_jid.userhostJID() | |
1081 if peer_jid == file_data['owner']: | |
1082 # the owner has all rights | |
1083 return | |
1084 if not C.ACCESS_PERMS.issuperset(perms_to_check): | |
1085 raise exceptions.InternalError(_(u'invalid permission')) | |
1086 | |
1087 for perm in perms_to_check: | |
1088 # we check each perm and raise PermissionError as soon as one condition is not valid | |
1089 # we must never return here, we only return after the loop if nothing was blocking the access | |
1090 try: | |
1091 perm_data = file_data[u'access'][perm] | |
1092 perm_type = perm_data[u'type'] | |
1093 except KeyError: | |
1094 raise failure.Failure(exceptions.PermissionError()) | |
1095 if perm_type == C.ACCESS_TYPE_PUBLIC: | |
1096 continue | |
1097 elif perm_type == C.ACCESS_TYPE_WHITELIST: | |
1098 try: | |
1099 jids = perm_data[u'jids'] | |
1100 except KeyError: | |
1101 raise failure.Failure(exceptions.PermissionError()) | |
1102 if peer_jid.full() in jids: | |
1103 continue | |
1104 else: | |
1105 raise failure.Failure(exceptions.PermissionError()) | |
1106 else: | |
1107 raise exceptions.InternalError(_(u'unknown access type: {type}').format(type=perm_type)) | |
1108 | |
1109 @defer.inlineCallbacks | |
1110 def checkPermissionToRoot(self, client, file_data, peer_jid, perms_to_check): | |
1111 """do checkFilePermission on file_data and all its parents until root""" | |
1112 current = file_data | |
1113 while True: | |
1114 self.checkFilePermission(current, peer_jid, perms_to_check) | |
1115 parent = current[u'parent'] | |
1116 if not parent: | |
1117 break | |
1118 files_data = yield self.getFile(self, client, peer_jid=None, file_id=parent, perms_to_check=None) | |
1119 try: | |
1120 current = files_data[0] | |
1121 except IndexError: | |
1122 raise exceptions.DataError(u'Missing parent') | |
1123 | |
1124 @defer.inlineCallbacks | |
1125 def _getParentDir(self, client, path, parent, namespace, owner, peer_jid, perms_to_check): | |
1126 """Retrieve parent node from a path, or last existing directory | |
1127 | |
1128 each directory of the path will be retrieved, until the last existing one | |
1129 @return (tuple[unicode, list[unicode])): parent, remaining path elements: | |
1130 - parent is the id of the last retrieved directory (or u'' for root) | |
1131 - remaining path elements are the directories which have not been retrieved | |
1132 (i.e. which don't exist) | |
1133 """ | |
1134 # if path is set, we have to retrieve parent directory of the file(s) from it | |
1135 if parent is not None: | |
1136 raise exceptions.ConflictError(_(u"You can't use path and parent at the same time")) | |
1137 path_elts = filter(None, path.split(u'/')) | |
1138 if {u'..', u'.'}.intersection(path_elts): | |
1139 raise ValueError(_(u'".." or "." can\'t be used in path')) | |
1140 | |
1141 # we retrieve all directories from path until we get the parent container | |
1142 # non existing directories will be created | |
1143 parent = u'' | |
1144 for idx, path_elt in enumerate(path_elts): | |
1145 directories = yield self.storage.getFiles(client, parent=parent, type_=C.FILE_TYPE_DIRECTORY, | |
1146 name=path_elt, namespace=namespace, owner=owner) | |
1147 if not directories: | |
1148 defer.returnValue((parent, path_elts[idx:])) | |
1149 # from this point, directories don't exist anymore, we have to create them | |
1150 elif len(directories) > 1: | |
1151 raise exceptions.InternalError(_(u"Several directories found, this should not happen")) | |
1152 else: | |
1153 directory = directories[0] | |
1154 self.checkFilePermission(directory, peer_jid, perms_to_check) | |
1155 parent = directory[u'id'] | |
1156 defer.returnValue((parent, [])) | |
1157 | |
1158 @defer.inlineCallbacks | |
1159 def getFiles(self, client, peer_jid, file_id=None, version=None, parent=None, path=None, type_=None, | |
1160 file_hash=None, hash_algo=None, name=None, namespace=None, mime_type=None, | |
1161 owner=None, access=None, projection=None, unique=False, perms_to_check=(C.ACCESS_PERM_READ,)): | |
1162 """retrieve files with with given filters | |
1163 | |
1164 @param peer_jid(jid.JID, None): jid trying to access the file | |
1165 needed to check permission. | |
1166 Use None to ignore permission (perms_to_check must be None too) | |
1167 @param file_id(unicode, None): id of the file | |
1168 None to ignore | |
1169 @param version(unicode, None): version of the file | |
1170 None to ignore | |
1171 empty string to look for current version | |
1172 @param parent(unicode, None): id of the directory containing the files | |
1173 None to ignore | |
1174 empty string to look for root files/directories | |
1175 @param projection(list[unicode], None): name of columns to retrieve | |
1176 None to retrieve all | |
1177 @param unique(bool): if True will remove duplicates | |
1178 @param perms_to_check(tuple[unicode],None): permission to check | |
1179 must be a tuple of C.ACCESS_PERM_* or None | |
1180 if None, permission will no be checked (peer_jid must be None too in this case) | |
1181 other params are the same as for [setFile] | |
1182 @return (list[dict]): files corresponding to filters | |
1183 @raise exceptions.NotFound: parent directory not found (when path is specified) | |
1184 @raise exceptions.PermissionError: peer_jid can't use perms_to_check for one of the file | |
1185 on the path | |
1186 """ | |
1187 if peer_jid is None and perms_to_check or perms_to_check is None and peer_jid: | |
1188 raise exceptions.InternalError('if you want to disable permission check, both peer_jid and perms_to_check must be None') | |
1189 if path is not None: | |
1190 # permission are checked by _getParentDir | |
1191 parent, remaining_path_elts = yield self._getParentDir(client, path, parent, namespace, owner, peer_jid, perms_to_check) | |
1192 if remaining_path_elts: | |
1193 # if we have remaining path elements, | |
1194 # the parent directory is not found | |
1195 raise failure.Failure(exceptions.NotFound()) | |
1196 if parent and peer_jid: | |
1197 # if parent is given directly and permission check is need, | |
1198 # we need to check all the parents | |
1199 parent_data = yield self.storage.getFiles(client, file_id=parent) | |
1200 try: | |
1201 parent_data = parent_data[0] | |
1202 except IndexError: | |
1203 raise exceptions.DataError(u'mising parent') | |
1204 yield self.checkPermissionToRoot(client, parent_data, peer_jid, perms_to_check) | |
1205 | |
1206 files = yield self.storage.getFiles(client, file_id=file_id, version=version, parent=parent, type_=type_, | |
1207 file_hash=file_hash, hash_algo=hash_algo, name=name, namespace=namespace, | |
1208 mime_type=mime_type, owner=owner, access=access, | |
1209 projection=projection, unique=unique) | |
1210 | |
1211 if peer_jid: | |
1212 # if permission are checked, we must remove all file tha use can't access | |
1213 to_remove = [] | |
1214 for file_data in files: | |
1215 try: | |
1216 self.checkFilePermission(file_data, peer_jid, perms_to_check) | |
1217 except exceptions.PermissionError: | |
1218 to_remove.append(file_data) | |
1219 for file_data in to_remove: | |
1220 files.remove(file_data) | |
1221 defer.returnValue(files) | |
1222 | |
1223 @defer.inlineCallbacks | |
1224 def setFile(self, client, name, file_id=None, version=u'', parent=None, path=None, | |
1225 type_=C.FILE_TYPE_FILE, file_hash=None, hash_algo=None, size=None, namespace=None, | |
1226 mime_type=None, created=None, modified=None, owner=None, access=None, extra=None, | |
1227 peer_jid = None, perms_to_check=(C.ACCESS_PERM_WRITE,)): | |
1228 """set a file metadata | |
1229 | |
1230 @param name(unicode): basename of the file | |
1231 @param file_id(unicode): unique id of the file | |
1232 @param version(unicode): version of this file | |
1233 empty string for current version or when there is no versioning | |
1234 @param parent(unicode, None): id of the directory containing the files | |
1235 @param path(unicode, None): virtual path of the file in the namespace | |
1236 if set, parent must be None. All intermediate directories will be created if needed, | |
1237 using current access. | |
1238 @param file_hash(unicode): unique hash of the payload | |
1239 @param hash_algo(unicode): algorithm used for hashing the file (usually sha-256) | |
1240 @param size(int): size in bytes | |
1241 @param namespace(unicode, None): identifier (human readable is better) to group files | |
1242 for instance, namespace could be used to group files in a specific photo album | |
1243 @param mime_type(unicode): MIME type of the file, or None if not known/guessed | |
1244 @param created(int): UNIX time of creation | |
1245 @param modified(int,None): UNIX time of last modification, or None to use created date | |
1246 @param owner(jid.JID, None): jid of the owner of the file (mainly useful for component) | |
1247 will be used to check permission. | |
1248 Use None to ignore permission (perms_to_check must be None too) | |
1249 @param access(dict, None): serialisable dictionary with access rules. | |
1250 None (or empty dict) to use private access, i.e. allow only profile's jid to access the file | |
1251 key can be on on C.ACCESS_PERM_*, | |
1252 then a sub dictionary with a type key is used (one of C.ACCESS_TYPE_*). | |
1253 According to type, extra keys can be used: | |
1254 - C.ACCESS_TYPE_PUBLIC: the permission is granted for everybody | |
1255 - C.ACCESS_TYPE_WHITELIST: the permission is granted for jids (as unicode) in the 'jids' key | |
1256 will be encoded to json in database | |
1257 @param extra(dict, None): serialisable dictionary of any extra data | |
1258 will be encoded to json in database | |
1259 @param perms_to_check(tuple[unicode],None): permission to check | |
1260 must be a tuple of C.ACCESS_PERM_* or None | |
1261 if None, permission will no be checked (peer_jid must be None too in this case) | |
1262 @param profile(unicode): profile owning the file | |
1263 """ | |
1264 if '/' in name: | |
1265 raise ValueError('name must not contain a slash ("/")') | |
1266 if file_id is None: | |
1267 file_id = shortuuid.uuid() | |
1268 if file_hash is not None and hash_algo is None or hash_algo is not None and file_hash is None: | |
1269 raise ValueError('file_hash and hash_algo must be set at the same time') | |
1270 if mime_type is None: | |
1271 mime_type, file_encoding = mimetypes.guess_type(name) | |
1272 if created is None: | |
1273 created = time.time() | |
1274 if namespace is not None: | |
1275 namespace = namespace.strip() or None | |
1276 if type_ == C.FILE_TYPE_DIRECTORY: | |
1277 if any(version, file_hash, size, mime_type): | |
1278 raise ValueError(u"version, file_hash, size and mime_type can't be set for a directory") | |
1279 | |
1280 if path is not None: | |
1281 if peer_jid is None: | |
1282 peer_jid = owner | |
1283 # _getParentDir will check permissions if peer_jid is set | |
1284 parent, remaining_path_elts = self._getParentDir(client, path, parent, namespace, owner, owner, perms_to_check) | |
1285 # if remaining directories don't exist, we have to create them | |
1286 for new_dir in remaining_path_elts: | |
1287 new_dir_id = shortuuid.uuid() | |
1288 yield self.storage.setFile(client, name=new_dir, file_id=new_dir_id, version=u'', parent=parent, | |
1289 type_=C.FILE_TYPE_DIRECTORY, namespace=namespace, | |
1290 created=time.time(), | |
1291 owner=owner.userhostJID() if owner else None, | |
1292 access=access, extra={}) | |
1293 parent = new_dir_id | |
1294 elif parent is None: | |
1295 parent = u'' | |
1296 | |
1297 yield self.storage.setFile(client, file_id=file_id, version=version, parent=parent, type_=type_, | |
1298 file_hash=file_hash, hash_algo=hash_algo, name=name, size=size, | |
1299 namespace=namespace, mime_type=mime_type, created=created, modified=modified, | |
1300 owner=owner.userhostJID() if owner else None, | |
1301 access=access, extra=extra) | |
1302 | |
1061 ## Misc ## | 1303 ## Misc ## |
1062 | 1304 |
1063 def isEntityAvailable(self, entity_jid, profile_key): | 1305 def isEntityAvailable(self, entity_jid, profile_key): |
1064 """Tell from the presence information if the given entity is available. | 1306 """Tell from the presence information if the given entity is available. |
1065 | 1307 |