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_,