Mercurial > libervia-backend
comparison sat/memory/memory.py @ 3715:b9718216a1c0 0.9
merge bookmark 0.9
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 01 Dec 2021 16:13:31 +0100 |
parents | 71516731d0aa |
children | 001ea5f4a2f9 |
comparison
equal
deleted
inserted
replaced
3714:af09b5aaa5d7 | 3715:b9718216a1c0 |
---|---|
31 from twisted.words.protocols.jabber import jid | 31 from twisted.words.protocols.jabber import jid |
32 from sat.core.i18n import _ | 32 from sat.core.i18n import _ |
33 from sat.core.log import getLogger | 33 from sat.core.log import getLogger |
34 from sat.core import exceptions | 34 from sat.core import exceptions |
35 from sat.core.constants import Const as C | 35 from sat.core.constants import Const as C |
36 from sat.memory.sqlite import SqliteStorage | 36 from sat.memory.sqla import Storage |
37 from sat.memory.persistent import PersistentDict | 37 from sat.memory.persistent import PersistentDict |
38 from sat.memory.params import Params | 38 from sat.memory.params import Params |
39 from sat.memory.disco import Discovery | 39 from sat.memory.disco import Discovery |
40 from sat.memory.crypto import BlockCipher | 40 from sat.memory.crypto import BlockCipher |
41 from sat.memory.crypto import PasswordHasher | 41 from sat.memory.crypto import PasswordHasher |
221 log.debug( | 221 log.debug( |
222 "FIXME: PasswordSessions should ask for the profile password after the session expired" | 222 "FIXME: PasswordSessions should ask for the profile password after the session expired" |
223 ) | 223 ) |
224 | 224 |
225 | 225 |
226 class Memory(object): | 226 class Memory: |
227 """This class manage all the persistent information""" | 227 """This class manage all the persistent information""" |
228 | 228 |
229 def __init__(self, host): | 229 def __init__(self, host): |
230 log.info(_("Memory manager init")) | 230 log.info(_("Memory manager init")) |
231 self.initialized = defer.Deferred() | |
232 self.host = host | 231 self.host = host |
233 self._entities_cache = {} # XXX: keep presence/last resource/other data in cache | 232 self._entities_cache = {} # XXX: keep presence/last resource/other data in cache |
234 # /!\ an entity is not necessarily in roster | 233 # /!\ an entity is not necessarily in roster |
235 # main key is bare jid, value is a dict | 234 # main key is bare jid, value is a dict |
236 # where main key is resource, or None for bare jid | 235 # where main key is resource, or None for bare jid |
238 self.subscriptions = {} | 237 self.subscriptions = {} |
239 self.auth_sessions = PasswordSessions() # remember the authenticated profiles | 238 self.auth_sessions = PasswordSessions() # remember the authenticated profiles |
240 self.disco = Discovery(host) | 239 self.disco = Discovery(host) |
241 self.config = tools_config.parseMainConf(log_filenames=True) | 240 self.config = tools_config.parseMainConf(log_filenames=True) |
242 self._cache_path = Path(self.getConfig("", "local_dir"), C.CACHE_DIR) | 241 self._cache_path = Path(self.getConfig("", "local_dir"), C.CACHE_DIR) |
243 database_file = os.path.expanduser( | 242 |
244 os.path.join(self.getConfig("", "local_dir"), C.SAVEFILE_DATABASE) | 243 async def initialise(self): |
245 ) | 244 self.storage = Storage() |
246 self.storage = SqliteStorage(database_file, host.version) | 245 await self.storage.initialise() |
247 PersistentDict.storage = self.storage | 246 PersistentDict.storage = self.storage |
248 self.params = Params(host, self.storage) | 247 self.params = Params(self.host, self.storage) |
249 log.info(_("Loading default params template")) | 248 log.info(_("Loading default params template")) |
250 self.params.load_default_params() | 249 self.params.load_default_params() |
251 d = self.storage.initialized.addCallback(lambda ignore: self.load()) | 250 await self.load() |
252 self.memory_data = PersistentDict("memory") | 251 self.memory_data = PersistentDict("memory") |
253 d.addCallback(lambda ignore: self.memory_data.load()) | 252 await self.memory_data.load() |
254 d.addCallback(lambda ignore: self.disco.load()) | 253 await self.disco.load() |
255 d.chainDeferred(self.initialized) | 254 |
256 | 255 |
257 ## Configuration ## | 256 ## Configuration ## |
258 | 257 |
259 def getConfig(self, section, name, default=None): | 258 def getConfig(self, section, name, default=None): |
260 """Get the main configuration option | 259 """Get the main configuration option |
1127 return self.params._getParamsValuesFromCategory( | 1126 return self.params._getParamsValuesFromCategory( |
1128 category, security_limit, app, extra_s, profile_key | 1127 category, security_limit, app, extra_s, profile_key |
1129 ) | 1128 ) |
1130 | 1129 |
1131 def asyncGetStringParamA( | 1130 def asyncGetStringParamA( |
1132 self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, | 1131 self, name, category, attribute="value", security_limit=C.NO_SECURITY_LIMIT, |
1133 profile_key=C.PROF_KEY_NONE): | 1132 profile_key=C.PROF_KEY_NONE): |
1134 | 1133 |
1135 profile = self.getProfileName(profile_key) | 1134 profile = self.getProfileName(profile_key) |
1136 return defer.ensureDeferred(self.params.asyncGetStringParamA( | 1135 return defer.ensureDeferred(self.params.asyncGetStringParamA( |
1137 name, category, attr, security_limit, profile | 1136 name, category, attribute, security_limit, profile |
1138 )) | 1137 )) |
1139 | 1138 |
1140 def _getParamsUI(self, security_limit, app, extra_s, profile_key): | 1139 def _getParamsUI(self, security_limit, app, extra_s, profile_key): |
1141 return self.params._getParamsUI(security_limit, app, extra_s, profile_key) | 1140 return self.params._getParamsUI(security_limit, app, extra_s, profile_key) |
1142 | 1141 |
1166 | 1165 |
1167 def _privateDataSet(self, namespace, key, data_s, profile_key): | 1166 def _privateDataSet(self, namespace, key, data_s, profile_key): |
1168 client = self.host.getClient(profile_key) | 1167 client = self.host.getClient(profile_key) |
1169 # we accept any type | 1168 # we accept any type |
1170 data = data_format.deserialise(data_s, type_check=None) | 1169 data = data_format.deserialise(data_s, type_check=None) |
1171 return self.storage.setPrivateValue( | 1170 return defer.ensureDeferred(self.storage.setPrivateValue( |
1172 namespace, key, data, binary=True, profile=client.profile) | 1171 namespace, key, data, binary=True, profile=client.profile)) |
1173 | 1172 |
1174 def _privateDataGet(self, namespace, key, profile_key): | 1173 def _privateDataGet(self, namespace, key, profile_key): |
1175 client = self.host.getClient(profile_key) | 1174 client = self.host.getClient(profile_key) |
1176 d = self.storage.getPrivates( | 1175 d = defer.ensureDeferred( |
1177 namespace, [key], binary=True, profile=client.profile) | 1176 self.storage.getPrivates( |
1177 namespace, [key], binary=True, profile=client.profile) | |
1178 ) | |
1178 d.addCallback(lambda data_dict: data_format.serialise(data_dict.get(key))) | 1179 d.addCallback(lambda data_dict: data_format.serialise(data_dict.get(key))) |
1179 return d | 1180 return d |
1180 | 1181 |
1181 def _privateDataDelete(self, namespace, key, profile_key): | 1182 def _privateDataDelete(self, namespace, key, profile_key): |
1182 client = self.host.getClient(profile_key) | 1183 client = self.host.getClient(profile_key) |
1183 return self.storage.delPrivateValue( | 1184 return defer.ensureDeferred(self.storage.delPrivateValue( |
1184 namespace, key, binary=True, profile=client.profile) | 1185 namespace, key, binary=True, profile=client.profile)) |
1185 | 1186 |
1186 ## Files ## | 1187 ## Files ## |
1187 | 1188 |
1188 def checkFilePermission( | 1189 def checkFilePermission( |
1189 self, | 1190 self, |
1247 else: | 1248 else: |
1248 raise exceptions.InternalError( | 1249 raise exceptions.InternalError( |
1249 _("unknown access type: {type}").format(type=perm_type) | 1250 _("unknown access type: {type}").format(type=perm_type) |
1250 ) | 1251 ) |
1251 | 1252 |
1252 @defer.inlineCallbacks | 1253 async def checkPermissionToRoot(self, client, file_data, peer_jid, perms_to_check): |
1253 def checkPermissionToRoot(self, client, file_data, peer_jid, perms_to_check): | |
1254 """do checkFilePermission on file_data and all its parents until root""" | 1254 """do checkFilePermission on file_data and all its parents until root""" |
1255 current = file_data | 1255 current = file_data |
1256 while True: | 1256 while True: |
1257 self.checkFilePermission(current, peer_jid, perms_to_check) | 1257 self.checkFilePermission(current, peer_jid, perms_to_check) |
1258 parent = current["parent"] | 1258 parent = current["parent"] |
1259 if not parent: | 1259 if not parent: |
1260 break | 1260 break |
1261 files_data = yield self.getFiles( | 1261 files_data = await self.getFiles( |
1262 client, peer_jid=None, file_id=parent, perms_to_check=None | 1262 client, peer_jid=None, file_id=parent, perms_to_check=None |
1263 ) | 1263 ) |
1264 try: | 1264 try: |
1265 current = files_data[0] | 1265 current = files_data[0] |
1266 except IndexError: | 1266 except IndexError: |
1267 raise exceptions.DataError("Missing parent") | 1267 raise exceptions.DataError("Missing parent") |
1268 | 1268 |
1269 @defer.inlineCallbacks | 1269 async def _getParentDir( |
1270 def _getParentDir( | |
1271 self, client, path, parent, namespace, owner, peer_jid, perms_to_check | 1270 self, client, path, parent, namespace, owner, peer_jid, perms_to_check |
1272 ): | 1271 ): |
1273 """Retrieve parent node from a path, or last existing directory | 1272 """Retrieve parent node from a path, or last existing directory |
1274 | 1273 |
1275 each directory of the path will be retrieved, until the last existing one | 1274 each directory of the path will be retrieved, until the last existing one |
1289 | 1288 |
1290 # we retrieve all directories from path until we get the parent container | 1289 # we retrieve all directories from path until we get the parent container |
1291 # non existing directories will be created | 1290 # non existing directories will be created |
1292 parent = "" | 1291 parent = "" |
1293 for idx, path_elt in enumerate(path_elts): | 1292 for idx, path_elt in enumerate(path_elts): |
1294 directories = yield self.storage.getFiles( | 1293 directories = await self.storage.getFiles( |
1295 client, | 1294 client, |
1296 parent=parent, | 1295 parent=parent, |
1297 type_=C.FILE_TYPE_DIRECTORY, | 1296 type_=C.FILE_TYPE_DIRECTORY, |
1298 name=path_elt, | 1297 name=path_elt, |
1299 namespace=namespace, | 1298 namespace=namespace, |
1300 owner=owner, | 1299 owner=owner, |
1301 ) | 1300 ) |
1302 if not directories: | 1301 if not directories: |
1303 defer.returnValue((parent, path_elts[idx:])) | 1302 return (parent, path_elts[idx:]) |
1304 # from this point, directories don't exist anymore, we have to create them | 1303 # from this point, directories don't exist anymore, we have to create them |
1305 elif len(directories) > 1: | 1304 elif len(directories) > 1: |
1306 raise exceptions.InternalError( | 1305 raise exceptions.InternalError( |
1307 _("Several directories found, this should not happen") | 1306 _("Several directories found, this should not happen") |
1308 ) | 1307 ) |
1309 else: | 1308 else: |
1310 directory = directories[0] | 1309 directory = directories[0] |
1311 self.checkFilePermission(directory, peer_jid, perms_to_check) | 1310 self.checkFilePermission(directory, peer_jid, perms_to_check) |
1312 parent = directory["id"] | 1311 parent = directory["id"] |
1313 defer.returnValue((parent, [])) | 1312 return (parent, []) |
1314 | 1313 |
1315 def getFileAffiliations(self, file_data: dict) -> Dict[jid.JID, str]: | 1314 def getFileAffiliations(self, file_data: dict) -> Dict[jid.JID, str]: |
1316 """Convert file access to pubsub like affiliations""" | 1315 """Convert file access to pubsub like affiliations""" |
1317 affiliations = {} | 1316 affiliations = {} |
1318 access_data = file_data['access'] | 1317 access_data = file_data['access'] |
1480 raise exceptions.InternalError( | 1479 raise exceptions.InternalError( |
1481 "Owner must be set for component if peer_jid is None" | 1480 "Owner must be set for component if peer_jid is None" |
1482 ) | 1481 ) |
1483 return peer_jid.userhostJID() | 1482 return peer_jid.userhostJID() |
1484 | 1483 |
1485 @defer.inlineCallbacks | 1484 async def getFiles( |
1486 def getFiles( | |
1487 self, client, peer_jid, file_id=None, version=None, parent=None, path=None, | 1485 self, client, peer_jid, file_id=None, version=None, parent=None, path=None, |
1488 type_=None, file_hash=None, hash_algo=None, name=None, namespace=None, | 1486 type_=None, file_hash=None, hash_algo=None, name=None, namespace=None, |
1489 mime_type=None, public_id=None, owner=None, access=None, projection=None, | 1487 mime_type=None, public_id=None, owner=None, access=None, projection=None, |
1490 unique=False, perms_to_check=(C.ACCESS_PERM_READ,)): | 1488 unique=False, perms_to_check=(C.ACCESS_PERM_READ,)): |
1491 """Retrieve files with with given filters | 1489 """Retrieve files with with given filters |
1532 ) | 1530 ) |
1533 owner = self.getFilesOwner(client, owner, peer_jid, file_id, parent) | 1531 owner = self.getFilesOwner(client, owner, peer_jid, file_id, parent) |
1534 if path is not None: | 1532 if path is not None: |
1535 path = str(path) | 1533 path = str(path) |
1536 # permission are checked by _getParentDir | 1534 # permission are checked by _getParentDir |
1537 parent, remaining_path_elts = yield self._getParentDir( | 1535 parent, remaining_path_elts = await self._getParentDir( |
1538 client, path, parent, namespace, owner, peer_jid, perms_to_check | 1536 client, path, parent, namespace, owner, peer_jid, perms_to_check |
1539 ) | 1537 ) |
1540 if remaining_path_elts: | 1538 if remaining_path_elts: |
1541 # if we have remaining path elements, | 1539 # if we have remaining path elements, |
1542 # the parent directory is not found | 1540 # the parent directory is not found |
1543 raise failure.Failure(exceptions.NotFound()) | 1541 raise failure.Failure(exceptions.NotFound()) |
1544 if parent and peer_jid: | 1542 if parent and peer_jid: |
1545 # if parent is given directly and permission check is requested, | 1543 # if parent is given directly and permission check is requested, |
1546 # we need to check all the parents | 1544 # we need to check all the parents |
1547 parent_data = yield self.storage.getFiles(client, file_id=parent) | 1545 parent_data = await self.storage.getFiles(client, file_id=parent) |
1548 try: | 1546 try: |
1549 parent_data = parent_data[0] | 1547 parent_data = parent_data[0] |
1550 except IndexError: | 1548 except IndexError: |
1551 raise exceptions.DataError("mising parent") | 1549 raise exceptions.DataError("mising parent") |
1552 yield self.checkPermissionToRoot( | 1550 await self.checkPermissionToRoot( |
1553 client, parent_data, peer_jid, perms_to_check | 1551 client, parent_data, peer_jid, perms_to_check |
1554 ) | 1552 ) |
1555 | 1553 |
1556 files = yield self.storage.getFiles( | 1554 files = await self.storage.getFiles( |
1557 client, | 1555 client, |
1558 file_id=file_id, | 1556 file_id=file_id, |
1559 version=version, | 1557 version=version, |
1560 parent=parent, | 1558 parent=parent, |
1561 type_=type_, | 1559 type_=type_, |
1574 if peer_jid: | 1572 if peer_jid: |
1575 # if permission are checked, we must remove all file that user can't access | 1573 # if permission are checked, we must remove all file that user can't access |
1576 to_remove = [] | 1574 to_remove = [] |
1577 for file_data in files: | 1575 for file_data in files: |
1578 try: | 1576 try: |
1579 self.checkFilePermission(file_data, peer_jid, perms_to_check, set_affiliation=True) | 1577 self.checkFilePermission( |
1578 file_data, peer_jid, perms_to_check, set_affiliation=True | |
1579 ) | |
1580 except exceptions.PermissionError: | 1580 except exceptions.PermissionError: |
1581 to_remove.append(file_data) | 1581 to_remove.append(file_data) |
1582 for file_data in to_remove: | 1582 for file_data in to_remove: |
1583 files.remove(file_data) | 1583 files.remove(file_data) |
1584 defer.returnValue(files) | 1584 return files |
1585 | 1585 |
1586 @defer.inlineCallbacks | 1586 async def setFile( |
1587 def setFile( | |
1588 self, client, name, file_id=None, version="", parent=None, path=None, | 1587 self, client, name, file_id=None, version="", parent=None, path=None, |
1589 type_=C.FILE_TYPE_FILE, file_hash=None, hash_algo=None, size=None, | 1588 type_=C.FILE_TYPE_FILE, file_hash=None, hash_algo=None, size=None, |
1590 namespace=None, mime_type=None, public_id=None, created=None, modified=None, | 1589 namespace=None, mime_type=None, public_id=None, created=None, modified=None, |
1591 owner=None, access=None, extra=None, peer_jid=None, | 1590 owner=None, access=None, extra=None, peer_jid=None, |
1592 perms_to_check=(C.ACCESS_PERM_WRITE,) | 1591 perms_to_check=(C.ACCESS_PERM_WRITE,) |
1664 owner = self.getFilesOwner(client, owner, peer_jid, file_id, parent) | 1663 owner = self.getFilesOwner(client, owner, peer_jid, file_id, parent) |
1665 | 1664 |
1666 if path is not None: | 1665 if path is not None: |
1667 path = str(path) | 1666 path = str(path) |
1668 # _getParentDir will check permissions if peer_jid is set, so we use owner | 1667 # _getParentDir will check permissions if peer_jid is set, so we use owner |
1669 parent, remaining_path_elts = yield self._getParentDir( | 1668 parent, remaining_path_elts = await self._getParentDir( |
1670 client, path, parent, namespace, owner, owner, perms_to_check | 1669 client, path, parent, namespace, owner, owner, perms_to_check |
1671 ) | 1670 ) |
1672 # if remaining directories don't exist, we have to create them | 1671 # if remaining directories don't exist, we have to create them |
1673 for new_dir in remaining_path_elts: | 1672 for new_dir in remaining_path_elts: |
1674 new_dir_id = shortuuid.uuid() | 1673 new_dir_id = shortuuid.uuid() |
1675 yield self.storage.setFile( | 1674 await self.storage.setFile( |
1676 client, | 1675 client, |
1677 name=new_dir, | 1676 name=new_dir, |
1678 file_id=new_dir_id, | 1677 file_id=new_dir_id, |
1679 version="", | 1678 version="", |
1680 parent=parent, | 1679 parent=parent, |
1687 ) | 1686 ) |
1688 parent = new_dir_id | 1687 parent = new_dir_id |
1689 elif parent is None: | 1688 elif parent is None: |
1690 parent = "" | 1689 parent = "" |
1691 | 1690 |
1692 yield self.storage.setFile( | 1691 await self.storage.setFile( |
1693 client, | 1692 client, |
1694 file_id=file_id, | 1693 file_id=file_id, |
1695 version=version, | 1694 version=version, |
1696 parent=parent, | 1695 parent=parent, |
1697 type_=type_, | 1696 type_=type_, |