2020-04-02 09:02:33 +00:00
|
|
|
__filename__ = "cache.py"
|
|
|
|
__author__ = "Bob Mottram"
|
|
|
|
__license__ = "AGPL3+"
|
2021-01-26 10:07:42 +00:00
|
|
|
__version__ = "1.2.0"
|
2020-04-02 09:02:33 +00:00
|
|
|
__maintainer__ = "Bob Mottram"
|
2021-09-10 16:14:50 +00:00
|
|
|
__email__ = "bob@libreserver.org"
|
2020-04-02 09:02:33 +00:00
|
|
|
__status__ = "Production"
|
2021-06-26 11:16:41 +00:00
|
|
|
__module_group__ = "Core"
|
2019-06-30 15:03:26 +00:00
|
|
|
|
2019-08-20 09:16:03 +00:00
|
|
|
import os
|
2019-06-30 15:18:40 +00:00
|
|
|
import datetime
|
2021-02-14 13:43:05 +00:00
|
|
|
from session import urlExists
|
2021-07-31 11:56:28 +00:00
|
|
|
from session import getJson
|
2019-10-22 11:55:06 +00:00
|
|
|
from utils import loadJson
|
|
|
|
from utils import saveJson
|
2020-05-04 18:39:21 +00:00
|
|
|
from utils import getFileCaseInsensitive
|
2021-07-31 11:56:28 +00:00
|
|
|
from utils import getUserPaths
|
2020-04-02 09:02:33 +00:00
|
|
|
|
2020-05-04 19:16:11 +00:00
|
|
|
|
2021-12-25 16:17:53 +00:00
|
|
|
def _removePersonFromCache(base_dir: str, personUrl: str,
|
2021-12-25 22:17:49 +00:00
|
|
|
person_cache: {}) -> bool:
|
2021-02-14 13:01:52 +00:00
|
|
|
"""Removes an actor from the cache
|
|
|
|
"""
|
2021-12-25 16:17:53 +00:00
|
|
|
cacheFilename = base_dir + '/cache/actors/' + \
|
2021-06-22 12:42:52 +00:00
|
|
|
personUrl.replace('/', '#') + '.json'
|
2021-02-14 13:01:52 +00:00
|
|
|
if os.path.isfile(cacheFilename):
|
|
|
|
try:
|
|
|
|
os.remove(cacheFilename)
|
2021-11-25 18:42:38 +00:00
|
|
|
except OSError:
|
2021-10-29 16:31:20 +00:00
|
|
|
print('EX: unable to delete cached actor ' + str(cacheFilename))
|
2021-12-25 22:17:49 +00:00
|
|
|
if person_cache.get(personUrl):
|
|
|
|
del person_cache[personUrl]
|
2021-02-14 13:01:52 +00:00
|
|
|
|
|
|
|
|
2021-12-25 16:17:53 +00:00
|
|
|
def checkForChangedActor(session, base_dir: str,
|
2021-12-26 10:00:46 +00:00
|
|
|
http_prefix: str, domain_full: str,
|
2021-12-25 22:17:49 +00:00
|
|
|
personUrl: str, avatarUrl: str, person_cache: {},
|
2021-02-14 13:43:05 +00:00
|
|
|
timeoutSec: int):
|
|
|
|
"""Checks if the avatar url exists and if not then
|
|
|
|
the actor has probably changed without receiving an actor/Person Update.
|
|
|
|
So clear the actor from the cache and it will be refreshed when the next
|
|
|
|
post from them is sent
|
|
|
|
"""
|
|
|
|
if not session or not avatarUrl:
|
|
|
|
return
|
2021-12-26 10:00:46 +00:00
|
|
|
if domain_full in avatarUrl:
|
2021-02-14 13:43:05 +00:00
|
|
|
return
|
2021-12-26 10:00:46 +00:00
|
|
|
if urlExists(session, avatarUrl, timeoutSec, http_prefix, domain_full):
|
2021-02-14 13:43:05 +00:00
|
|
|
return
|
2021-12-25 22:17:49 +00:00
|
|
|
_removePersonFromCache(base_dir, personUrl, person_cache)
|
2021-02-14 13:43:05 +00:00
|
|
|
|
|
|
|
|
2021-12-25 16:17:53 +00:00
|
|
|
def storePersonInCache(base_dir: str, personUrl: str,
|
2021-12-25 22:17:49 +00:00
|
|
|
personJson: {}, person_cache: {},
|
2020-08-29 10:21:29 +00:00
|
|
|
allowWriteToFile: bool) -> None:
|
2019-06-30 15:03:26 +00:00
|
|
|
"""Store an actor in the cache
|
|
|
|
"""
|
2020-12-12 09:54:42 +00:00
|
|
|
if 'statuses' in personUrl or personUrl.endswith('/actor'):
|
|
|
|
# This is not an actor or person account
|
|
|
|
return
|
|
|
|
|
2020-04-02 09:02:33 +00:00
|
|
|
currTime = datetime.datetime.utcnow()
|
2021-12-25 22:17:49 +00:00
|
|
|
person_cache[personUrl] = {
|
2019-07-06 17:00:22 +00:00
|
|
|
"actor": personJson,
|
|
|
|
"timestamp": currTime.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
}
|
2021-12-25 16:17:53 +00:00
|
|
|
if not base_dir:
|
2019-08-20 09:16:03 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
# store to file
|
2021-06-22 11:25:28 +00:00
|
|
|
if not allowWriteToFile:
|
|
|
|
return
|
2021-12-25 16:17:53 +00:00
|
|
|
if os.path.isdir(base_dir + '/cache/actors'):
|
|
|
|
cacheFilename = base_dir + '/cache/actors/' + \
|
2021-06-22 12:42:52 +00:00
|
|
|
personUrl.replace('/', '#') + '.json'
|
2021-06-22 11:25:28 +00:00
|
|
|
if not os.path.isfile(cacheFilename):
|
|
|
|
saveJson(personJson, cacheFilename)
|
2020-04-02 09:02:33 +00:00
|
|
|
|
2019-06-30 15:03:26 +00:00
|
|
|
|
2021-12-25 22:17:49 +00:00
|
|
|
def getPersonFromCache(base_dir: str, personUrl: str, person_cache: {},
|
2020-08-29 10:21:29 +00:00
|
|
|
allowWriteToFile: bool) -> {}:
|
2019-06-30 15:03:26 +00:00
|
|
|
"""Get an actor from the cache
|
|
|
|
"""
|
2019-08-20 09:37:09 +00:00
|
|
|
# if the actor is not in memory then try to load it from file
|
2020-04-02 09:02:33 +00:00
|
|
|
loadedFromFile = False
|
2021-12-25 22:17:49 +00:00
|
|
|
if not person_cache.get(personUrl):
|
2020-08-29 10:21:29 +00:00
|
|
|
# does the person exist as a cached file?
|
2021-12-25 16:17:53 +00:00
|
|
|
cacheFilename = base_dir + '/cache/actors/' + \
|
2021-06-22 12:42:52 +00:00
|
|
|
personUrl.replace('/', '#') + '.json'
|
2020-08-29 19:54:30 +00:00
|
|
|
actorFilename = getFileCaseInsensitive(cacheFilename)
|
|
|
|
if actorFilename:
|
|
|
|
personJson = loadJson(actorFilename)
|
2019-09-02 11:20:15 +00:00
|
|
|
if personJson:
|
2021-12-25 16:17:53 +00:00
|
|
|
storePersonInCache(base_dir, personUrl, personJson,
|
2021-12-25 22:17:49 +00:00
|
|
|
person_cache, False)
|
2020-04-02 09:02:33 +00:00
|
|
|
loadedFromFile = True
|
2020-03-22 21:16:02 +00:00
|
|
|
|
2021-12-25 22:17:49 +00:00
|
|
|
if person_cache.get(personUrl):
|
2019-08-20 09:50:27 +00:00
|
|
|
if not loadedFromFile:
|
|
|
|
# update the timestamp for the last time the actor was retrieved
|
2020-04-02 09:02:33 +00:00
|
|
|
currTime = datetime.datetime.utcnow()
|
|
|
|
currTimeStr = currTime.strftime("%Y-%m-%dT%H:%M:%SZ")
|
2021-12-25 22:17:49 +00:00
|
|
|
person_cache[personUrl]['timestamp'] = currTimeStr
|
|
|
|
return person_cache[personUrl]['actor']
|
2019-08-20 09:37:09 +00:00
|
|
|
return None
|
|
|
|
|
2020-04-02 09:02:33 +00:00
|
|
|
|
2021-12-25 22:17:49 +00:00
|
|
|
def expirePersonCache(person_cache: {}):
|
2019-08-20 09:37:09 +00:00
|
|
|
"""Expires old entries from the cache in memory
|
|
|
|
"""
|
2020-04-02 09:02:33 +00:00
|
|
|
currTime = datetime.datetime.utcnow()
|
|
|
|
removals = []
|
2021-12-25 22:17:49 +00:00
|
|
|
for personUrl, cacheJson in person_cache.items():
|
2020-04-02 09:02:33 +00:00
|
|
|
cacheTime = datetime.datetime.strptime(cacheJson['timestamp'],
|
|
|
|
"%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
daysSinceCached = (currTime - cacheTime).days
|
2019-08-20 09:37:09 +00:00
|
|
|
if daysSinceCached > 2:
|
|
|
|
removals.append(personUrl)
|
2020-04-02 09:02:33 +00:00
|
|
|
if len(removals) > 0:
|
2019-08-20 09:37:09 +00:00
|
|
|
for personUrl in removals:
|
2021-12-25 22:17:49 +00:00
|
|
|
del person_cache[personUrl]
|
2020-04-02 09:02:33 +00:00
|
|
|
print(str(len(removals)) + ' actors were expired from the cache')
|
2019-08-20 09:37:09 +00:00
|
|
|
|
2020-04-02 09:02:33 +00:00
|
|
|
|
2021-12-25 22:28:18 +00:00
|
|
|
def storeWebfingerInCache(handle: str, wf, cached_webfingers: {}) -> None:
|
2019-08-20 09:37:09 +00:00
|
|
|
"""Store a webfinger endpoint in the cache
|
|
|
|
"""
|
2021-12-25 22:28:18 +00:00
|
|
|
cached_webfingers[handle] = wf
|
2020-04-02 09:02:33 +00:00
|
|
|
|
2019-06-30 15:03:26 +00:00
|
|
|
|
2021-12-25 22:28:18 +00:00
|
|
|
def getWebfingerFromCache(handle: str, cached_webfingers: {}) -> {}:
|
2019-06-30 15:03:26 +00:00
|
|
|
"""Get webfinger endpoint from the cache
|
|
|
|
"""
|
2021-12-25 22:28:18 +00:00
|
|
|
if cached_webfingers.get(handle):
|
|
|
|
return cached_webfingers[handle]
|
2019-06-30 15:03:26 +00:00
|
|
|
return None
|
2021-07-31 11:56:28 +00:00
|
|
|
|
|
|
|
|
2021-12-25 16:17:53 +00:00
|
|
|
def getPersonPubKey(base_dir: str, session, personUrl: str,
|
2021-12-25 22:17:49 +00:00
|
|
|
person_cache: {}, debug: bool,
|
2021-12-25 20:34:38 +00:00
|
|
|
project_version: str, http_prefix: str,
|
2021-12-25 20:43:43 +00:00
|
|
|
domain: str, onion_domain: str,
|
2021-12-25 23:03:28 +00:00
|
|
|
signing_priv_key_pem: str) -> str:
|
2021-07-31 11:56:28 +00:00
|
|
|
if not personUrl:
|
|
|
|
return None
|
|
|
|
personUrl = personUrl.replace('#main-key', '')
|
|
|
|
usersPaths = getUserPaths()
|
|
|
|
for possibleUsersPath in usersPaths:
|
|
|
|
if personUrl.endswith(possibleUsersPath + 'inbox'):
|
|
|
|
if debug:
|
|
|
|
print('DEBUG: Obtaining public key for shared inbox')
|
|
|
|
personUrl = \
|
|
|
|
personUrl.replace(possibleUsersPath + 'inbox', '/inbox')
|
|
|
|
break
|
|
|
|
personJson = \
|
2021-12-25 22:17:49 +00:00
|
|
|
getPersonFromCache(base_dir, personUrl, person_cache, True)
|
2021-07-31 11:56:28 +00:00
|
|
|
if not personJson:
|
|
|
|
if debug:
|
|
|
|
print('DEBUG: Obtaining public key for ' + personUrl)
|
|
|
|
personDomain = domain
|
2021-12-25 20:43:43 +00:00
|
|
|
if onion_domain:
|
2021-07-31 11:56:28 +00:00
|
|
|
if '.onion/' in personUrl:
|
2021-12-25 20:43:43 +00:00
|
|
|
personDomain = onion_domain
|
2021-07-31 11:56:28 +00:00
|
|
|
profileStr = 'https://www.w3.org/ns/activitystreams'
|
|
|
|
asHeader = {
|
|
|
|
'Accept': 'application/activity+json; profile="' + profileStr + '"'
|
|
|
|
}
|
|
|
|
personJson = \
|
2021-12-25 23:03:28 +00:00
|
|
|
getJson(signing_priv_key_pem,
|
2021-08-31 14:17:11 +00:00
|
|
|
session, personUrl, asHeader, None, debug,
|
2021-12-25 20:34:38 +00:00
|
|
|
project_version, http_prefix, personDomain)
|
2021-07-31 11:56:28 +00:00
|
|
|
if not personJson:
|
|
|
|
return None
|
|
|
|
pubKey = None
|
|
|
|
if personJson.get('publicKey'):
|
|
|
|
if personJson['publicKey'].get('publicKeyPem'):
|
|
|
|
pubKey = personJson['publicKey']['publicKeyPem']
|
|
|
|
else:
|
|
|
|
if personJson.get('publicKeyPem'):
|
|
|
|
pubKey = personJson['publicKeyPem']
|
|
|
|
|
|
|
|
if not pubKey:
|
|
|
|
if debug:
|
|
|
|
print('DEBUG: Public key not found for ' + personUrl)
|
|
|
|
|
2021-12-25 22:17:49 +00:00
|
|
|
storePersonInCache(base_dir, personUrl, personJson, person_cache, True)
|
2021-07-31 11:56:28 +00:00
|
|
|
return pubKey
|