Validate sending actor of incoming post

merge-requests/30/head
Bob Mottram 2021-12-14 13:27:00 +00:00
parent f8b62c5388
commit 0e0160a698
8 changed files with 136 additions and 9 deletions

View File

@ -869,7 +869,7 @@ def _desktopShowProfile(session, nickname: str, domain: str,
isHttp = True isHttp = True
actorJson, asHeader = \ actorJson, asHeader = \
getActorJson(domain, actor, isHttp, False, False, True, getActorJson(domain, actor, isHttp, False, False, True,
signingPrivateKeyPem) signingPrivateKeyPem, session)
_desktopShowActor(baseDir, actorJson, translate, _desktopShowActor(baseDir, actorJson, translate,
systemLanguage, screenreader, espeak) systemLanguage, screenreader, espeak)
@ -890,7 +890,7 @@ def _desktopShowProfileFromHandle(session, nickname: str, domain: str,
""" """
actorJson, asHeader = \ actorJson, asHeader = \
getActorJson(domain, handle, False, False, False, True, getActorJson(domain, handle, False, False, False, True,
signingPrivateKeyPem) signingPrivateKeyPem, session)
_desktopShowActor(baseDir, actorJson, translate, _desktopShowActor(baseDir, actorJson, translate,
systemLanguage, screenreader, espeak) systemLanguage, screenreader, espeak)

View File

@ -2086,7 +2086,7 @@ if args.actor:
else: else:
print('Did not obtain instance actor key for ' + domain) print('Did not obtain instance actor key for ' + domain)
getActorJson(domain, args.actor, args.http, args.gnunet, getActorJson(domain, args.actor, args.http, args.gnunet,
debug, False, signingPrivateKeyPem) debug, False, signingPrivateKeyPem, None)
sys.exit() sys.exit()
if args.followers: if args.followers:

View File

@ -144,6 +144,20 @@ def isFilteredGlobally(baseDir: str, content: str) -> bool:
return False return False
def isFilteredBio(baseDir: str, nickname: str, domain: str, bio: str) -> bool:
"""Should the given actor bio be filtered out?
"""
if isFilteredGlobally(baseDir, bio):
return True
if not nickname or not domain:
return False
accountFiltersFilename = \
acctDir(baseDir, nickname, domain) + '/filters_bio.txt'
return _isFilteredBase(accountFiltersFilename, bio)
def isFiltered(baseDir: str, nickname: str, domain: str, content: str) -> bool: def isFiltered(baseDir: str, nickname: str, domain: str, content: str) -> bool:
"""Should the given content be filtered out? """Should the given content be filtered out?
This is a simple type of filter which just matches words, not a regex This is a simple type of filter which just matches words, not a regex

View File

@ -113,6 +113,7 @@ from notifyOnPost import notifyWhenPersonPosts
from conversation import updateConversation from conversation import updateConversation
from content import validHashTag from content import validHashTag
from webapp_hashtagswarm import htmlHashTagSwarm from webapp_hashtagswarm import htmlHashTagSwarm
from person import validSendingActor
def _storeLastPostId(baseDir: str, nickname: str, domain: str, def _storeLastPostId(baseDir: str, nickname: str, domain: str,
@ -3408,6 +3409,11 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
allowLocalNetworkAccess, debug, allowLocalNetworkAccess, debug,
systemLanguage, httpPrefix, systemLanguage, httpPrefix,
domainFull, personCache): domainFull, personCache):
# is the sending actor valid?
if not validSendingActor(session, baseDir, nickname, domain,
personCache, postJsonObject,
signingPrivateKeyPem, debug, unitTest):
return False
if postJsonObject.get('object'): if postJsonObject.get('object'):
jsonObj = postJsonObject['object'] jsonObj = postJsonObject['object']

View File

@ -86,7 +86,7 @@ def _updateMovedHandle(baseDir: str, nickname: str, domain: str,
gnunet = True gnunet = True
personJson = \ personJson = \
getActorJson(domain, personUrl, httpPrefix, gnunet, debug, False, getActorJson(domain, personUrl, httpPrefix, gnunet, debug, False,
signingPrivateKeyPem) signingPrivateKeyPem, None)
if not personJson: if not personJson:
return ctr return ctr
if not personJson.get('movedTo'): if not personJson.get('movedTo'):

111
person.py
View File

@ -38,6 +38,8 @@ from roles import setRole
from roles import setRolesFromList from roles import setRolesFromList
from roles import getActorRolesList from roles import getActorRolesList
from media import processMetaData from media import processMetaData
from utils import removeHtml
from utils import containsInvalidChars
from utils import replaceUsersWithAt from utils import replaceUsersWithAt
from utils import removeLineEndings from utils import removeLineEndings
from utils import removeDomainPort from utils import removeDomainPort
@ -63,6 +65,8 @@ from session import getJson
from webfinger import webfingerHandle from webfinger import webfingerHandle
from pprint import pprint from pprint import pprint
from cache import getPersonFromCache from cache import getPersonFromCache
from cache import storePersonInCache
from filters import isFilteredBio
def generateRSAKey() -> (str, str): def generateRSAKey() -> (str, str):
@ -1415,7 +1419,8 @@ def _detectUsersPath(url: str) -> str:
def getActorJson(hostDomain: str, handle: str, http: bool, gnunet: bool, def getActorJson(hostDomain: str, handle: str, http: bool, gnunet: bool,
debug: bool, quiet: bool, debug: bool, quiet: bool,
signingPrivateKeyPem: str) -> ({}, {}): signingPrivateKeyPem: str,
existingSession) -> ({}, {}):
"""Returns the actor json """Returns the actor json
""" """
if debug: if debug:
@ -1498,7 +1503,10 @@ def getActorJson(hostDomain: str, handle: str, http: bool, gnunet: bool,
httpPrefix = 'https' httpPrefix = 'https'
else: else:
httpPrefix = 'http' httpPrefix = 'http'
session = createSession(proxyType) if existingSession:
session = existingSession
else:
session = createSession(proxyType)
if nickname == 'inbox': if nickname == 'inbox':
nickname = domain nickname = domain
@ -1634,3 +1642,102 @@ def addActorUpdateTimestamp(actorJson: {}) -> None:
# add updated timestamp to avatar and banner # add updated timestamp to avatar and banner
actorJson['icon']['updated'] = currDateStr actorJson['icon']['updated'] = currDateStr
actorJson['image']['updated'] = currDateStr actorJson['image']['updated'] = currDateStr
def validSendingActor(session, baseDir: str,
nickname: str, domain: str,
personCache: {},
postJsonObject: {},
signingPrivateKeyPem: str,
debug: bool, unitTest: bool) -> bool:
"""When a post arrives in the inbox this is used to check that
the sending actor is valid
"""
# who sent this post?
sendingActor = postJsonObject['actor']
# sending to yourself (reminder)
if sendingActor.endswith(domain + '/users/' + nickname):
return True
# get their actor
actorJson = getPersonFromCache(baseDir, sendingActor, personCache, True)
downloadedActor = False
if not actorJson:
# download the actor
actorJson, _ = getActorJson(domain, sendingActor,
True, False, debug, True,
signingPrivateKeyPem, session)
if actorJson:
downloadedActor = True
if not actorJson:
# if the actor couldn't be obtained then proceed anyway
return True
if not actorJson.get('name'):
print('REJECT: no name within actor ' + str(actorJson))
return False
if not actorJson.get('preferredUsername'):
print('REJECT: no preferredUsername within actor ' + str(actorJson))
return False
# does the actor have a bio ?
if not unitTest:
if not actorJson.get('summary'):
# allow no bio if it's an actor in this instance
if domain not in sendingActor:
# probably a spam actor with no bio
print('REJECT: spam actor ' + sendingActor)
return False
bioStr = removeHtml(actorJson['summary'])
bioStr += ' ' + removeHtml(actorJson['preferredUsername'])
bioStr += ' ' + removeHtml(actorJson['name'])
if containsInvalidChars(bioStr):
print('REJECT: post actor bio contains invalid characters')
return False
if isFilteredBio(baseDir, nickname, domain, bioStr):
print('REJECT: post actor bio contains filtered text')
return False
else:
print('Skipping check for missing bio in ' + sendingActor)
# Check any attached fields for the actor.
# Spam actors will sometimes have attached fields which are all empty
if actorJson.get('attachment'):
if isinstance(actorJson['attachment'], list):
noOfTags = 0
tagsWithoutValue = 0
for tag in actorJson['attachment']:
if not isinstance(tag, dict):
continue
if not tag.get('name'):
continue
noOfTags += 1
if not tag.get('value'):
tagsWithoutValue += 1
continue
if not isinstance(tag['value'], str):
tagsWithoutValue += 1
continue
if not tag['value'].strip():
tagsWithoutValue += 1
continue
if len(tag['value']) < 2:
tagsWithoutValue += 1
continue
if containsInvalidChars(tag['name']):
tagsWithoutValue += 1
continue
if containsInvalidChars(tag['value']):
tagsWithoutValue += 1
continue
if noOfTags > 0:
if int(tagsWithoutValue * 100 / noOfTags) > 50:
print('REJECT: actor has empty attachments ' +
sendingActor)
return False
if downloadedActor:
# if the actor is valid and was downloaded then
# store it in the cache, but don't write it to file
storePersonInCache(baseDir, sendingActor, actorJson, personCache,
False)
return True

4
pgp.py
View File

@ -343,7 +343,7 @@ def _getPGPPublicKeyFromActor(signingPrivateKeyPem: str,
if not actorJson: if not actorJson:
actorJson, asHeader = \ actorJson, asHeader = \
getActorJson(domain, handle, False, False, False, True, getActorJson(domain, handle, False, False, False, True,
signingPrivateKeyPem) signingPrivateKeyPem, None)
if not actorJson: if not actorJson:
return None return None
if not actorJson.get('attachment'): if not actorJson.get('attachment'):
@ -491,7 +491,7 @@ def pgpPublicKeyUpload(baseDir: str, session,
actorJson, asHeader = \ actorJson, asHeader = \
getActorJson(domainFull, handle, False, False, debug, True, getActorJson(domainFull, handle, False, False, debug, True,
signingPrivateKeyPem) signingPrivateKeyPem, session)
if not actorJson: if not actorJson:
if debug: if debug:
print('No actor returned for ' + handle) print('No actor returned for ' + handle)

View File

@ -151,7 +151,7 @@ def htmlProfileAfterSearch(cssCache: {},
gnunet = True gnunet = True
profileJson, asHeader = \ profileJson, asHeader = \
getActorJson(domain, profileHandle, http, gnunet, debug, False, getActorJson(domain, profileHandle, http, gnunet, debug, False,
signingPrivateKeyPem) signingPrivateKeyPem, session)
if not profileJson: if not profileJson:
return None return None