Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon

main
Bob Mottram 2021-01-04 21:35:58 +00:00
commit 96fe9f0513
28 changed files with 770 additions and 234 deletions

View File

@ -177,6 +177,7 @@ from shares import addShare
from shares import removeShare from shares import removeShare
from shares import expireShares from shares import expireShares
from categories import setHashtagCategory from categories import setHashtagCategory
from utils import getLockedAccount
from utils import hasUsersPath from utils import hasUsersPath
from utils import getFullDomain from utils import getFullDomain
from utils import removeHtml from utils import removeHtml
@ -1078,6 +1079,8 @@ class PubServer(BaseHTTPRequestHandler):
elif self.headers.get('content-length'): elif self.headers.get('content-length'):
headersDict['content-length'] = self.headers['content-length'] headersDict['content-length'] = self.headers['content-length']
originalMessageJson = messageJson.copy()
# For follow activities add a 'to' field, which is a copy # For follow activities add a 'to' field, which is a copy
# of the object field # of the object field
messageJson, toFieldExists = \ messageJson, toFieldExists = \
@ -1096,7 +1099,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.httpPrefix, self.server.httpPrefix,
nickname, nickname,
self.server.domainFull, self.server.domainFull,
messageJson, messageJson, originalMessageJson,
messageBytesDecoded, messageBytesDecoded,
headersDict, headersDict,
self.path, self.path,
@ -5214,11 +5217,13 @@ class PubServer(BaseHTTPRequestHandler):
jamiAddress = None jamiAddress = None
ssbAddress = None ssbAddress = None
emailAddress = None emailAddress = None
lockedAccount = False
actorJson = getPersonFromCache(baseDir, actorJson = getPersonFromCache(baseDir,
optionsActor, optionsActor,
self.server.personCache, self.server.personCache,
True) True)
if actorJson: if actorJson:
lockedAccount = getLockedAccount(actorJson)
donateUrl = getDonationUrl(actorJson) donateUrl = getDonationUrl(actorJson)
xmppAddress = getXmppAddress(actorJson) xmppAddress = getXmppAddress(actorJson)
matrixAddress = getMatrixAddress(actorJson) matrixAddress = getMatrixAddress(actorJson)
@ -5247,7 +5252,8 @@ class PubServer(BaseHTTPRequestHandler):
PGPpubKey, PGPfingerprint, PGPpubKey, PGPfingerprint,
emailAddress, emailAddress,
self.server.dormantMonths, self.server.dormantMonths,
backToPath).encode('utf-8') backToPath,
lockedAccount).encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
cookie, callingDomain) cookie, callingDomain)
@ -13229,6 +13235,12 @@ class PubServer(BaseHTTPRequestHandler):
if self.server.debug: if self.server.debug:
print('DEBUG: Check message has params') print('DEBUG: Check message has params')
if not messageJson:
self.send_response(403)
self.end_headers()
self.server.POSTbusy = False
return
if self.path.endswith('/inbox') or \ if self.path.endswith('/inbox') or \
self.path == '/sharedInbox': self.path == '/sharedInbox':
if not inboxMessageHasParams(messageJson): if not inboxMessageHasParams(messageJson):

View File

@ -11,7 +11,7 @@
# License # License
# ======= # =======
# #
# Copyright (C) 2020 Bob Mottram <bob@freedombone.net> # Copyright (C) 2020-2021 Bob Mottram <bob@freedombone.net>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by # it under the terms of the GNU Affero General Public License as published by

View File

@ -478,7 +478,6 @@ if args.debug:
if args.tests: if args.tests:
runAllTests() runAllTests()
sys.exit() sys.exit()
if args.testsnetwork: if args.testsnetwork:
print('Network Tests') print('Network Tests')
testPostMessageBetweenServers() testPostMessageBetweenServers()

View File

@ -175,7 +175,7 @@ def followerOfPerson(baseDir: str, nickname: str, domain: str,
federationList, debug, 'followers.txt') federationList, debug, 'followers.txt')
def _isFollowerOfPerson(baseDir: str, nickname: str, domain: str, def isFollowerOfPerson(baseDir: str, nickname: str, domain: str,
followerNickname: str, followerDomain: str) -> bool: followerNickname: str, followerDomain: str) -> bool:
"""is the given nickname a follower of followerNickname? """is the given nickname a follower of followerNickname?
""" """
@ -663,7 +663,7 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
baseDir + '/accounts/' + handleToFollow) baseDir + '/accounts/' + handleToFollow)
return True return True
if _isFollowerOfPerson(baseDir, if isFollowerOfPerson(baseDir,
nicknameToFollow, domainToFollowFull, nicknameToFollow, domainToFollowFull,
nickname, domainFull): nickname, domainFull):
if debug: if debug:

View File

@ -10,6 +10,7 @@ import json
import os import os
import datetime import datetime
import time import time
from linked_data_sig import verifyJsonSignature
from utils import hasUsersPath from utils import hasUsersPath
from utils import validPostDate from utils import validPostDate
from utils import getFullDomain from utils import getFullDomain
@ -312,6 +313,7 @@ def inboxPermittedMessage(domain: str, messageJson: {},
def savePostToInboxQueue(baseDir: str, httpPrefix: str, def savePostToInboxQueue(baseDir: str, httpPrefix: str,
nickname: str, domain: str, nickname: str, domain: str,
postJsonObject: {}, postJsonObject: {},
originalPostJsonObject: {},
messageBytes: str, messageBytes: str,
httpHeaders: {}, httpHeaders: {},
postPath: str, debug: bool) -> str: postPath: str, debug: bool) -> str:
@ -436,6 +438,7 @@ def savePostToInboxQueue(baseDir: str, httpPrefix: str,
'httpHeaders': httpHeaders, 'httpHeaders': httpHeaders,
'path': postPath, 'path': postPath,
'post': postJsonObject, 'post': postJsonObject,
'original': originalPostJsonObject,
'digest': digest, 'digest': digest,
'filename': filename, 'filename': filename,
'destination': destination 'destination': destination
@ -2679,9 +2682,9 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
queue.pop(0) queue.pop(0)
continue continue
# check the signature # check the http header signature
if debug: if debug:
print('DEBUG: checking http headers') print('DEBUG: checking http header signature')
pprint(queueJson['httpHeaders']) pprint(queueJson['httpHeaders'])
postStr = json.dumps(queueJson['post']) postStr = json.dumps(queueJson['post'])
if not verifyPostHeaders(httpPrefix, if not verifyPostHeaders(httpPrefix,
@ -2700,7 +2703,37 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
continue continue
if debug: if debug:
print('DEBUG: Signature check success') print('DEBUG: http header signature check success')
# check if a json signature exists on this post
checkJsonSignature = False
if queueJson['original'].get('@context') and \
queueJson['original'].get('signature'):
if isinstance(queueJson['original']['signature'], dict):
# see https://tools.ietf.org/html/rfc7515
jwebsig = queueJson['original']['signature']
# signature exists and is of the expected type
if jwebsig.get('type') and jwebsig.get('signatureValue'):
if jwebsig['type'] == 'RsaSignature2017':
checkJsonSignature = True
if checkJsonSignature:
# use the original json message received, not one which may have
# been modified along the way
if not verifyJsonSignature(queueJson['original'], pubKey):
if debug:
print('WARN: jsonld inbox signature check failed ' +
keyId + ' ' + pubKey + ' ' +
str(queueJson['original']))
else:
print('WARN: jsonld inbox signature check failed ' +
keyId)
if os.path.isfile(queueFilename):
os.remove(queueFilename)
if len(queue) > 0:
queue.pop(0)
continue
else:
print('jsonld inbox signature check success ' + keyId)
# set the id to the same as the post filename # set the id to the same as the post filename
# This makes the filename and the id consistent # This makes the filename and the id consistent

View File

@ -1,155 +0,0 @@
__filename__ = "jsonldsig.py"
__author__ = "Bob Mottram"
__credits__ = ['Based on ' +
'https://github.com/WebOfTrustInfo/ld-signatures-python']
__license__ = "AGPL3+"
__version__ = "1.1.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
from copy import deepcopy
from datetime import datetime
import pytz
try:
from Cryptodome.PublicKey import RSA
from Cryptodome.Hash import SHA256
from Cryptodome.Signature import pkcs1_5 as PKCS1_v1_5
except ImportError:
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256
from Crypto.Signature import PKCS1_v1_5
from pyjsonld import normalize
import base64
import json
def _b64safeEncode(payload: {}) -> str:
"""
b64 url safe encoding with the padding removed.
"""
return base64.urlsafe_b64encode(payload).rstrip(b'=')
def _b64safeDecode(payload: {}) -> str:
"""
b64 url safe decoding with the padding added.
"""
return base64.urlsafe_b64decode(payload + b'=' * (4 - len(payload) % 4))
def _normalizeJson(payload: {}) -> str:
"""
Normalize with URDNA2015
"""
return json.dumps(payload, separators=(',', ':'),
sort_keys=True).encode('utf-8')
def _signRs256(payload: {}, privateKeyPem: str) -> str:
"""
Produce a RS256 signature of the payload
"""
key = RSA.importKey(privateKeyPem)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(SHA256.new(payload))
return signature
def _verifyRs256(payload: {}, signature: str, publicKeyPem: str) -> bool:
"""
Verifies a RS256 signature
"""
key = RSA.importKey(publicKeyPem)
verifier = PKCS1_v1_5.new(key)
return verifier.verify(SHA256.new(payload), signature)
def _signJws(payload: {}, privateKeyPem: str) -> str:
"""
Prepare payload to sign
"""
header = {
'alg': 'RS256',
'b64': False,
'crit': ['b64']
}
normalizedJson = _normalizeJson(header)
encodedHeader = _b64safeEncode(normalizedJson)
preparedPayload = b'.'.join([encodedHeader, payload])
signature = _signRs256(preparedPayload, privateKeyPem)
encodedSignature = _b64safeEncode(signature)
jwsSignature = b'..'.join([encodedHeader, encodedSignature])
return jwsSignature
def _verifyJws(payload: {}, jwsSignature: str, publicKeyPem: str) -> bool:
"""
Verifies a signature using the given public key
"""
encodedHeader, encodedSignature = jwsSignature.split(b'..')
signature = _b64safeDecode(encodedSignature)
payload = b'.'.join([encodedHeader, payload])
return _verifyRs256(payload, signature, publicKeyPem)
def _jsonldNormalize(jldDocument: str):
"""
Normalize and hash the json-ld document
"""
options = {
'algorithm': 'URDNA2015',
'format': 'application/nquads'
}
normalized = normalize(jldDocument, options=options)
normalizedHash = SHA256.new(data=normalized.encode('utf-8')).digest()
return normalizedHash
def jsonldSign(jldDocument: {}, privateKeyPem: str) -> {}:
"""
Produces a signed JSON-LD document with a Json Web Signature
"""
jldDocument = deepcopy(jldDocument)
normalizedJldHash = _jsonldNormalize(jldDocument)
jwsSignature = _signJws(normalizedJldHash, privateKeyPem)
# construct the signature document and add it to jsonld
signature = {
'type': 'RsaSignatureSuite2017',
'created': datetime.now(tz=pytz.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
'signatureValue': jwsSignature.decode('utf-8')
}
jldDocument.update({'signature': signature})
return jldDocument
def jsonldVerify(signedJldDocument: {}, publicKeyPem: str) -> bool:
"""
Verifies the Json Web Signature of a signed JSON-LD Document
"""
signedJldDocument = deepcopy(signedJldDocument)
signature = signedJldDocument.pop('signature')
jwsSignature = signature['signatureValue'].encode('utf-8')
normalizedJldHash = _jsonldNormalize(signedJldDocument)
return _verifyJws(normalizedJldHash, jwsSignature, publicKeyPem)
def testSignJsonld(jldDocument: {}, privateKeyPem: str) -> {}:
"""
Creates a test signature
"""
signedJldDocument = jsonldSign(jldDocument, privateKeyPem)
# pop the created time key since its dynamic
signedJldDocument['signature'].pop('created')
return signedJldDocument

84
linked_data_sig.py 100644
View File

@ -0,0 +1,84 @@
__filename__ = "linked_data_sig.py"
__author__ = "Bob Mottram"
__credits__ = ['Based on ' +
'https://github.com/tsileo/little-boxes']
__license__ = "AGPL3+"
__version__ = "1.1.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
import base64
import hashlib
from datetime import datetime
try:
from Cryptodome.PublicKey import RSA
from Cryptodome.Hash import SHA256
from Cryptodome.Signature import pkcs1_5 as PKCS1_v1_5
except ImportError:
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256
from Crypto.Signature import PKCS1_v1_5
from pyjsonld import normalize
def _options_hash(doc):
doc = dict(doc["signature"])
for k in ["type", "id", "signatureValue"]:
if k in doc:
del doc[k]
doc["@context"] = "https://w3id.org/identity/v1"
options = {
"algorithm": "URDNA2015",
"format": "application/nquads"
}
normalized = normalize(doc, options)
h = hashlib.new("sha256")
h.update(normalized.encode("utf-8"))
return h.hexdigest()
def _doc_hash(doc):
doc = dict(doc)
if "signature" in doc:
del doc["signature"]
options = {
"algorithm": "URDNA2015",
"format": "application/nquads"
}
normalized = normalize(doc, options)
h = hashlib.new("sha256")
h.update(normalized.encode("utf-8"))
return h.hexdigest()
def verifyJsonSignature(doc: {}, publicKeyPem: str):
key = RSA.importKey(publicKeyPem)
to_be_signed = _options_hash(doc) + _doc_hash(doc)
signature = doc["signature"]["signatureValue"]
signer = PKCS1_v1_5.new(key) # type: ignore
digest = SHA256.new()
digest.update(to_be_signed.encode("utf-8"))
return signer.verify(digest, base64.b64decode(signature)) # type: ignore
def generateJsonSignature(doc: {}, privateKeyPem: str):
if not doc.get('actor'):
return
options = {
"type": "RsaSignature2017",
"creator": doc["actor"] + "#main-key",
"created": datetime.utcnow().replace(microsecond=0).isoformat() + "Z",
}
doc["signature"] = options
to_be_signed = _options_hash(doc) + _doc_hash(doc)
key = RSA.importKey(privateKeyPem)
signer = PKCS1_v1_5.new(key)
digest = SHA256.new()
digest.update(to_be_signed.encode("utf-8"))
sig = base64.b64encode(signer.sign(digest)) # type: ignore
options["signatureValue"] = sig.decode("utf-8")

View File

@ -65,7 +65,7 @@ from blocking import isBlocked
from blocking import isBlockedDomain from blocking import isBlockedDomain
from filters import isFiltered from filters import isFiltered
from git import convertPostToPatch from git import convertPostToPatch
from jsonldsig import jsonldSign from linked_data_sig import generateJsonSignature
from petnames import resolvePetnames from petnames import resolvePetnames
@ -1794,7 +1794,8 @@ def sendPost(projectVersion: str,
if not postJsonObject.get('signature'): if not postJsonObject.get('signature'):
try: try:
signedPostJsonObject = jsonldSign(postJsonObject, privateKeyPem) signedPostJsonObject = postJsonObject.copy()
generateJsonSignature(signedPostJsonObject, privateKeyPem)
postJsonObject = signedPostJsonObject postJsonObject = signedPostJsonObject
except Exception as e: except Exception as e:
print('WARN: failed to JSON-LD sign post, ' + str(e)) print('WARN: failed to JSON-LD sign post, ' + str(e))
@ -2122,7 +2123,8 @@ def sendSignedJson(postJsonObject: {}, session, baseDir: str,
if not postJsonObject.get('signature'): if not postJsonObject.get('signature'):
try: try:
signedPostJsonObject = jsonldSign(postJsonObject, privateKeyPem) signedPostJsonObject = postJsonObject.copy()
generateJsonSignature(signedPostJsonObject, privateKeyPem)
postJsonObject = signedPostJsonObject postJsonObject = signedPostJsonObject
except Exception as e: except Exception as e:
print('WARN: failed to JSON-LD sign post, ' + str(e)) print('WARN: failed to JSON-LD sign post, ' + str(e))

View File

@ -24,9 +24,7 @@ __all__ = [
'JsonLdProcessor', 'JsonLdError', 'ActiveContextCache'] 'JsonLdProcessor', 'JsonLdError', 'ActiveContextCache']
import copy import copy
import gzip
import hashlib import hashlib
import io
import json import json
import os import os
import posixpath import posixpath
@ -37,7 +35,6 @@ import string
import sys import sys
import traceback import traceback
from collections import deque, namedtuple from collections import deque, namedtuple
from contextlib import closing
from numbers import Integral, Real from numbers import Integral, Real
try: try:
@ -77,7 +74,6 @@ except ImportError:
# support python 2 # support python 2
if sys.version_info[0] >= 3: if sys.version_info[0] >= 3:
from urllib.request import build_opener as urllib_build_opener
from urllib.request import HTTPSHandler from urllib.request import HTTPSHandler
import urllib.parse as urllib_parse import urllib.parse as urllib_parse
from http.client import HTTPSConnection from http.client import HTTPSConnection
@ -86,7 +82,6 @@ if sys.version_info[0] >= 3:
def cmp(a, b): def cmp(a, b):
return (a > b) - (a < b) return (a > b) - (a < b)
else: else:
from urllib2 import build_opener as urllib_build_opener
from urllib2 import HTTPSHandler from urllib2 import HTTPSHandler
import urlparse as urllib_parse import urlparse as urllib_parse
from httplib import HTTPSConnection from httplib import HTTPSConnection
@ -234,7 +229,7 @@ def link(input_, ctx, options=None):
return frame(input, frame, options) return frame(input, frame, options)
def normalize(input_, options=None): def normalize(input_: {}, options=None):
""" """
Performs JSON-LD normalization. Performs JSON-LD normalization.
@ -355,6 +350,477 @@ def parse_link_header(header):
return rval return rval
def getV1Schema() -> {}:
# https://w3id.org/identity/v1
return {
"@context": {
"id": "@id",
"type": "@type",
"cred": "https://w3id.org/credentials#",
"dc": "http://purl.org/dc/terms/",
"identity": "https://w3id.org/identity#",
"perm": "https://w3id.org/permissions#",
"ps": "https://w3id.org/payswarm#",
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"sec": "https://w3id.org/security#",
"schema": "http://schema.org/",
"xsd": "http://www.w3.org/2001/XMLSchema#",
"Group": "https://www.w3.org/ns/activitystreams#Group",
"claim": {"@id": "cred:claim", "@type": "@id"},
"credential": {"@id": "cred:credential", "@type": "@id"},
"issued": {"@id": "cred:issued", "@type": "xsd:dateTime"},
"issuer": {"@id": "cred:issuer", "@type": "@id"},
"recipient": {"@id": "cred:recipient", "@type": "@id"},
"Credential": "cred:Credential",
"CryptographicKeyCredential": "cred:CryptographicKeyCredential",
"about": {"@id": "schema:about", "@type": "@id"},
"address": {"@id": "schema:address", "@type": "@id"},
"addressCountry": "schema:addressCountry",
"addressLocality": "schema:addressLocality",
"addressRegion": "schema:addressRegion",
"comment": "rdfs:comment",
"created": {"@id": "dc:created", "@type": "xsd:dateTime"},
"creator": {"@id": "dc:creator", "@type": "@id"},
"description": "schema:description",
"email": "schema:email",
"familyName": "schema:familyName",
"givenName": "schema:givenName",
"image": {"@id": "schema:image", "@type": "@id"},
"label": "rdfs:label",
"name": "schema:name",
"postalCode": "schema:postalCode",
"streetAddress": "schema:streetAddress",
"title": "dc:title",
"url": {"@id": "schema:url", "@type": "@id"},
"Person": "schema:Person",
"PostalAddress": "schema:PostalAddress",
"Organization": "schema:Organization",
"identityService": {
"@id": "identity:identityService", "@type": "@id"
},
"idp": {"@id": "identity:idp", "@type": "@id"},
"Identity": "identity:Identity",
"paymentProcessor": "ps:processor",
"preferences": {"@id": "ps:preferences", "@type": "@vocab"},
"cipherAlgorithm": "sec:cipherAlgorithm",
"cipherData": "sec:cipherData",
"cipherKey": "sec:cipherKey",
"digestAlgorithm": "sec:digestAlgorithm",
"digestValue": "sec:digestValue",
"domain": "sec:domain",
"expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
"initializationVector": "sec:initializationVector",
"member": {"@id": "schema:member", "@type": "@id"},
"memberOf": {"@id": "schema:memberOf", "@type": "@id"},
"nonce": "sec:nonce",
"normalizationAlgorithm": "sec:normalizationAlgorithm",
"owner": {"@id": "sec:owner", "@type": "@id"},
"password": "sec:password",
"privateKey": {"@id": "sec:privateKey", "@type": "@id"},
"privateKeyPem": "sec:privateKeyPem",
"publicKey": {"@id": "sec:publicKey", "@type": "@id"},
"publicKeyPem": "sec:publicKeyPem",
"publicKeyService": {
"@id": "sec:publicKeyService", "@type": "@id"
},
"revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"},
"signature": "sec:signature",
"signatureAlgorithm": "sec:signatureAlgorithm",
"signatureValue": "sec:signatureValue",
"CryptographicKey": "sec:Key",
"EncryptedMessage": "sec:EncryptedMessage",
"GraphSignature2012": "sec:GraphSignature2012",
"LinkedDataSignature2015": "sec:LinkedDataSignature2015",
"accessControl": {"@id": "perm:accessControl", "@type": "@id"},
"writePermission": {"@id": "perm:writePermission", "@type": "@id"}
}
}
def getActivitystreamsSchema() -> {}:
# https://www.w3.org/ns/activitystreams
return {
"@context": {
"@vocab": "_:",
"xsd": "http://www.w3.org/2001/XMLSchema#",
"as": "https://www.w3.org/ns/activitystreams#",
"ldp": "http://www.w3.org/ns/ldp#",
"vcard": "http://www.w3.org/2006/vcard/ns#",
"id": "@id",
"type": "@type",
"Accept": "as:Accept",
"Activity": "as:Activity",
"IntransitiveActivity": "as:IntransitiveActivity",
"Add": "as:Add",
"Announce": "as:Announce",
"Application": "as:Application",
"Arrive": "as:Arrive",
"Article": "as:Article",
"Audio": "as:Audio",
"Block": "as:Block",
"Collection": "as:Collection",
"CollectionPage": "as:CollectionPage",
"Relationship": "as:Relationship",
"Create": "as:Create",
"Delete": "as:Delete",
"Dislike": "as:Dislike",
"Document": "as:Document",
"Event": "as:Event",
"Follow": "as:Follow",
"Flag": "as:Flag",
"Group": "as:Group",
"Ignore": "as:Ignore",
"Image": "as:Image",
"Invite": "as:Invite",
"Join": "as:Join",
"Leave": "as:Leave",
"Like": "as:Like",
"Link": "as:Link",
"Mention": "as:Mention",
"Note": "as:Note",
"Object": "as:Object",
"Offer": "as:Offer",
"OrderedCollection": "as:OrderedCollection",
"OrderedCollectionPage": "as:OrderedCollectionPage",
"Organization": "as:Organization",
"Page": "as:Page",
"Person": "as:Person",
"Place": "as:Place",
"Profile": "as:Profile",
"Question": "as:Question",
"Reject": "as:Reject",
"Remove": "as:Remove",
"Service": "as:Service",
"TentativeAccept": "as:TentativeAccept",
"TentativeReject": "as:TentativeReject",
"Tombstone": "as:Tombstone",
"Undo": "as:Undo",
"Update": "as:Update",
"Video": "as:Video",
"View": "as:View",
"Listen": "as:Listen",
"Read": "as:Read",
"Move": "as:Move",
"Travel": "as:Travel",
"IsFollowing": "as:IsFollowing",
"IsFollowedBy": "as:IsFollowedBy",
"IsContact": "as:IsContact",
"IsMember": "as:IsMember",
"subject": {
"@id": "as:subject",
"@type": "@id"
},
"relationship": {
"@id": "as:relationship",
"@type": "@id"
},
"actor": {
"@id": "as:actor",
"@type": "@id"
},
"attributedTo": {
"@id": "as:attributedTo",
"@type": "@id"
},
"attachment": {
"@id": "as:attachment",
"@type": "@id"
},
"bcc": {
"@id": "as:bcc",
"@type": "@id"
},
"bto": {
"@id": "as:bto",
"@type": "@id"
},
"cc": {
"@id": "as:cc",
"@type": "@id"
},
"context": {
"@id": "as:context",
"@type": "@id"
},
"current": {
"@id": "as:current",
"@type": "@id"
},
"first": {
"@id": "as:first",
"@type": "@id"
},
"generator": {
"@id": "as:generator",
"@type": "@id"
},
"icon": {
"@id": "as:icon",
"@type": "@id"
},
"image": {
"@id": "as:image",
"@type": "@id"
},
"inReplyTo": {
"@id": "as:inReplyTo",
"@type": "@id"
},
"items": {
"@id": "as:items",
"@type": "@id"
},
"instrument": {
"@id": "as:instrument",
"@type": "@id"
},
"orderedItems": {
"@id": "as:items",
"@type": "@id",
"@container": "@list"
},
"last": {
"@id": "as:last",
"@type": "@id"
},
"location": {
"@id": "as:location",
"@type": "@id"
},
"next": {
"@id": "as:next",
"@type": "@id"
},
"object": {
"@id": "as:object",
"@type": "@id"
},
"oneOf": {
"@id": "as:oneOf",
"@type": "@id"
},
"anyOf": {
"@id": "as:anyOf",
"@type": "@id"
},
"closed": {
"@id": "as:closed",
"@type": "xsd:dateTime"
},
"origin": {
"@id": "as:origin",
"@type": "@id"
},
"accuracy": {
"@id": "as:accuracy",
"@type": "xsd:float"
},
"prev": {
"@id": "as:prev",
"@type": "@id"
},
"preview": {
"@id": "as:preview",
"@type": "@id"
},
"replies": {
"@id": "as:replies",
"@type": "@id"
},
"result": {
"@id": "as:result",
"@type": "@id"
},
"audience": {
"@id": "as:audience",
"@type": "@id"
},
"partOf": {
"@id": "as:partOf",
"@type": "@id"
},
"tag": {
"@id": "as:tag",
"@type": "@id"
},
"target": {
"@id": "as:target",
"@type": "@id"
},
"to": {
"@id": "as:to",
"@type": "@id"
},
"url": {
"@id": "as:url",
"@type": "@id"
},
"altitude": {
"@id": "as:altitude",
"@type": "xsd:float"
},
"content": "as:content",
"contentMap": {
"@id": "as:content",
"@container": "@language"
},
"name": "as:name",
"nameMap": {
"@id": "as:name",
"@container": "@language"
},
"duration": {
"@id": "as:duration",
"@type": "xsd:duration"
},
"endTime": {
"@id": "as:endTime",
"@type": "xsd:dateTime"
},
"height": {
"@id": "as:height",
"@type": "xsd:nonNegativeInteger"
},
"href": {
"@id": "as:href",
"@type": "@id"
},
"hreflang": "as:hreflang",
"latitude": {
"@id": "as:latitude",
"@type": "xsd:float"
},
"longitude": {
"@id": "as:longitude",
"@type": "xsd:float"
},
"mediaType": "as:mediaType",
"published": {
"@id": "as:published",
"@type": "xsd:dateTime"
},
"radius": {
"@id": "as:radius",
"@type": "xsd:float"
},
"rel": "as:rel",
"startIndex": {
"@id": "as:startIndex",
"@type": "xsd:nonNegativeInteger"
},
"startTime": {
"@id": "as:startTime",
"@type": "xsd:dateTime"
},
"summary": "as:summary",
"summaryMap": {
"@id": "as:summary",
"@container": "@language"
},
"totalItems": {
"@id": "as:totalItems",
"@type": "xsd:nonNegativeInteger"
},
"units": "as:units",
"updated": {
"@id": "as:updated",
"@type": "xsd:dateTime"
},
"width": {
"@id": "as:width",
"@type": "xsd:nonNegativeInteger"
},
"describes": {
"@id": "as:describes",
"@type": "@id"
},
"formerType": {
"@id": "as:formerType",
"@type": "@id"
},
"deleted": {
"@id": "as:deleted",
"@type": "xsd:dateTime"
},
"inbox": {
"@id": "ldp:inbox",
"@type": "@id"
},
"outbox": {
"@id": "as:outbox",
"@type": "@id"
},
"following": {
"@id": "as:following",
"@type": "@id"
},
"followers": {
"@id": "as:followers",
"@type": "@id"
},
"streams": {
"@id": "as:streams",
"@type": "@id"
},
"preferredUsername": "as:preferredUsername",
"endpoints": {
"@id": "as:endpoints",
"@type": "@id"
},
"uploadMedia": {
"@id": "as:uploadMedia",
"@type": "@id"
},
"proxyUrl": {
"@id": "as:proxyUrl",
"@type": "@id"
},
"liked": {
"@id": "as:liked",
"@type": "@id"
},
"oauthAuthorizationEndpoint": {
"@id": "as:oauthAuthorizationEndpoint",
"@type": "@id"
},
"oauthTokenEndpoint": {
"@id": "as:oauthTokenEndpoint",
"@type": "@id"
},
"provideClientKey": {
"@id": "as:provideClientKey",
"@type": "@id"
},
"signClientKey": {
"@id": "as:signClientKey",
"@type": "@id"
},
"sharedInbox": {
"@id": "as:sharedInbox",
"@type": "@id"
},
"Public": {
"@id": "as:Public",
"@type": "@id"
},
"source": "as:source",
"likes": {
"@id": "as:likes",
"@type": "@id"
},
"shares": {
"@id": "as:shares",
"@type": "@id"
},
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
}
}
}
def load_document(url): def load_document(url):
""" """
Retrieves JSON-LD at the given URL. Retrieves JSON-LD at the given URL.
@ -367,49 +833,30 @@ def load_document(url):
# validate URL # validate URL
pieces = urllib_parse.urlparse(url) pieces = urllib_parse.urlparse(url)
if (not all([pieces.scheme, pieces.netloc]) or if (not all([pieces.scheme, pieces.netloc]) or
pieces.scheme not in ['http', 'https'] or pieces.scheme not in ['http', 'https', 'dat'] or
set(pieces.netloc) > set( set(pieces.netloc) > set(
string.ascii_letters + string.digits + '-.:')): string.ascii_letters + string.digits + '-.:')):
raise JsonLdError( raise JsonLdError(
'URL could not be dereferenced; only "http" and "https" ' 'URL could not be dereferenced; only http/https/dat '
'URLs are supported.', 'URLs are supported.',
'jsonld.InvalidUrl', {'url': url}, 'jsonld.InvalidUrl', {'url': url},
code='loading document failed') code='loading document failed')
https_handler = VerifiedHTTPSHandler()
url_opener = urllib_build_opener(https_handler) if url == 'https://w3id.org/identity/v1':
url_opener.addheaders = [
('Accept', 'application/ld+json, application/json'),
('Accept-Encoding', 'deflate')]
with closing(url_opener.open(url)) as handle:
if handle.info().get('Content-Encoding') == 'gzip':
buf = io.BytesIO(handle.read())
f = gzip.GzipFile(fileobj=buf, mode='rb')
data = f.read()
else:
data = handle.read()
doc = { doc = {
'contextUrl': None, 'contextUrl': None,
'documentUrl': url, 'documentUrl': url,
'document': data.decode('utf8') 'document': getV1Schema()
} }
doc['documentUrl'] = handle.geturl()
headers = dict(handle.info())
content_type = headers.get('content-type')
link_header = headers.get('link')
if link_header and content_type != 'application/ld+json':
link_header = parse_link_header(link_header).get(
LINK_HEADER_REL)
# only 1 related link header permitted
if isinstance(link_header, list):
raise JsonLdError(
'URL could not be dereferenced, it has more than one '
'associated HTTP Link Header.',
'jsonld.LoadDocumentError',
{'url': url},
code='multiple context link headers')
if link_header:
doc['contextUrl'] = link_header['target']
return doc return doc
elif url == 'https://www.w3.org/ns/activitystreams':
doc = {
'contextUrl': None,
'documentUrl': url,
'document': getActivitystreamsSchema()
}
return doc
return None
except JsonLdError as e: except JsonLdError as e:
raise e raise e
except Exception as cause: except Exception as cause:

View File

@ -86,8 +86,8 @@ from content import replaceContentDuplicates
from content import removeTextFormatting from content import removeTextFormatting
from content import removeHtmlTag from content import removeHtmlTag
from theme import setCSSparam from theme import setCSSparam
from jsonldsig import testSignJsonld from linked_data_sig import generateJsonSignature
from jsonldsig import jsonldVerify from linked_data_sig import verifyJsonSignature
from newsdaemon import hashtagRuleTree from newsdaemon import hashtagRuleTree
from newsdaemon import hashtagRuleResolve from newsdaemon import hashtagRuleResolve
from newswire import getNewswireTags from newswire import getNewswireTags
@ -1977,11 +1977,14 @@ def testRemoveTextFormatting():
def testJsonld(): def testJsonld():
print("testJsonld") print("testJsonld")
jldDocument = { jldDocument = {
"@context": "https://www.w3.org/ns/activitystreams",
"actor": "https://somesite.net/users/gerbil",
"description": "My json document", "description": "My json document",
"numberField": 83582, "numberField": 83582,
"object": { "object": {
"content": "Some content" "content": "valid content"
} }
} }
# privateKeyPem, publicKeyPem = generateRSAKey() # privateKeyPem, publicKeyPem = generateRSAKey()
@ -2022,14 +2025,43 @@ def testJsonld():
'TwIDAQAB\n' \ 'TwIDAQAB\n' \
'-----END PUBLIC KEY-----' '-----END PUBLIC KEY-----'
signedDocument = testSignJsonld(jldDocument, privateKeyPem) signedDocument = jldDocument.copy()
generateJsonSignature(signedDocument, privateKeyPem)
assert(signedDocument) assert(signedDocument)
assert(signedDocument.get('signature')) assert(signedDocument.get('signature'))
assert(signedDocument['signature'].get('signatureValue')) assert(signedDocument['signature'].get('signatureValue'))
assert(signedDocument['signature'].get('type')) assert(signedDocument['signature'].get('type'))
assert(len(signedDocument['signature']['signatureValue']) > 50) assert(len(signedDocument['signature']['signatureValue']) > 50)
assert(signedDocument['signature']['type'] == 'RsaSignatureSuite2017') # print(str(signedDocument['signature']))
assert(jsonldVerify(signedDocument, publicKeyPem)) assert(signedDocument['signature']['type'] == 'RsaSignature2017')
assert(verifyJsonSignature(signedDocument, publicKeyPem))
# alter the signed document
signedDocument['object']['content'] = 'forged content'
assert(not verifyJsonSignature(signedDocument, publicKeyPem))
jldDocument2 = {
"@context": "https://www.w3.org/ns/activitystreams",
"actor": "https://somesite.net/users/gerbil",
"description": "Another json document",
"numberField": 13353,
"object": {
"content": "More content"
}
}
signedDocument2 = jldDocument2.copy()
generateJsonSignature(signedDocument2, privateKeyPem)
assert(signedDocument2)
assert(signedDocument2.get('signature'))
assert(signedDocument2['signature'].get('signatureValue'))
# changed signature on different document
if signedDocument['signature']['signatureValue'] == \
signedDocument2['signature']['signatureValue']:
print('json signature has not changed for different documents')
assert '.' not in str(signedDocument['signature']['signatureValue'])
assert len(str(signedDocument['signature']['signatureValue'])) > 340
assert(signedDocument['signature']['signatureValue'] !=
signedDocument2['signature']['signatureValue'])
def testSiteIsActive(): def testSiteIsActive():
@ -2978,9 +3010,36 @@ def testFunctions():
'-Gsep=+120 -Tx11 epicyon.dot') '-Gsep=+120 -Tx11 epicyon.dot')
def testLinksWithinPost() -> None:
baseDir = os.getcwd()
nickname = 'test27636'
domain = 'rando.site'
port = 443
httpPrefix = 'https'
content = 'This is a test post with links.\n\n' + \
'ftp://ftp.ncdc.noaa.gov/pub/data/ghcn/v4/\n\nhttps://freedombone.net'
postJsonObject = \
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
content,
False, False, False, True,
None, None, False, None)
assert postJsonObject['object']['content'] == \
'<p>This is a test post with links.<br><br>' + \
'<a href="ftp://ftp.ncdc.noaa.gov/pub/data/ghcn/v4/" ' + \
'rel="nofollow noopener noreferrer" target="_blank">' + \
'<span class="invisible">ftp://</span>' + \
'<span class="ellipsis">' + \
'ftp.ncdc.noaa.gov/pub/data/ghcn/v4/</span>' + \
'</a><br><br><a href="https://freedombone.net" ' + \
'rel="nofollow noopener noreferrer" target="_blank">' + \
'<span class="invisible">https://</span>' + \
'<span class="ellipsis">freedombone.net</span></a></p>'
def runAllTests(): def runAllTests():
print('Running tests...') print('Running tests...')
testFunctions() testFunctions()
testLinksWithinPost()
testReplyToPublicPost() testReplyToPublicPost()
testGetMentionedPeople() testGetMentionedPeople()
testGuessHashtagCategory() testGuessHashtagCategory()

View File

@ -349,5 +349,6 @@
"Unfilter words": "الكلمات غير المصفاة", "Unfilter words": "الكلمات غير المصفاة",
"Show Accounts": "إظهار الحسابات", "Show Accounts": "إظهار الحسابات",
"Peertube Instances": "مثيلات Peertube", "Peertube Instances": "مثيلات Peertube",
"Show video previews for the following Peertube sites.": "إظهار معاينات الفيديو لمواقع Peertube التالية." "Show video previews for the following Peertube sites.": "إظهار معاينات الفيديو لمواقع Peertube التالية.",
"Follows you": "يتبعك"
} }

View File

@ -349,5 +349,6 @@
"Unfilter words": "Paraules sense filtre", "Unfilter words": "Paraules sense filtre",
"Show Accounts": "Mostra comptes", "Show Accounts": "Mostra comptes",
"Peertube Instances": "Instàncies de Peertube", "Peertube Instances": "Instàncies de Peertube",
"Show video previews for the following Peertube sites.": "Mostra les previsualitzacions de vídeo dels següents llocs de Peertube." "Show video previews for the following Peertube sites.": "Mostra les previsualitzacions de vídeo dels següents llocs de Peertube.",
"Follows you": "Et segueix"
} }

View File

@ -349,5 +349,6 @@
"Unfilter words": "Geiriau di-hid", "Unfilter words": "Geiriau di-hid",
"Show Accounts": "Dangos Cyfrifon", "Show Accounts": "Dangos Cyfrifon",
"Peertube Instances": "Camau Peertube", "Peertube Instances": "Camau Peertube",
"Show video previews for the following Peertube sites.": "Dangos rhagolygon fideo ar gyfer y safleoedd Peertube canlynol." "Show video previews for the following Peertube sites.": "Dangos rhagolygon fideo ar gyfer y safleoedd Peertube canlynol.",
"Follows you": "Yn eich dilyn chi"
} }

View File

@ -349,5 +349,6 @@
"Unfilter words": "Wörter herausfiltern", "Unfilter words": "Wörter herausfiltern",
"Show Accounts": "Konten anzeigen", "Show Accounts": "Konten anzeigen",
"Peertube Instances": "Peertube-Instanzen", "Peertube Instances": "Peertube-Instanzen",
"Show video previews for the following Peertube sites.": "Zeigen Sie eine Videovorschau für die folgenden Peertube-Websites an." "Show video previews for the following Peertube sites.": "Zeigen Sie eine Videovorschau für die folgenden Peertube-Websites an.",
"Follows you": "Folgt dir"
} }

View File

@ -349,5 +349,6 @@
"Unfilter words": "Unfilter words", "Unfilter words": "Unfilter words",
"Show Accounts": "Show Accounts", "Show Accounts": "Show Accounts",
"Peertube Instances": "Peertube Instances", "Peertube Instances": "Peertube Instances",
"Show video previews for the following Peertube sites.": "Show video previews for the following Peertube sites." "Show video previews for the following Peertube sites.": "Show video previews for the following Peertube sites.",
"Follows you": "Follows you"
} }

View File

@ -349,5 +349,6 @@
"Unfilter words": "Palabras sin filtrar", "Unfilter words": "Palabras sin filtrar",
"Show Accounts": "Mostrar cuentas", "Show Accounts": "Mostrar cuentas",
"Peertube Instances": "Instancias de Peertube", "Peertube Instances": "Instancias de Peertube",
"Show video previews for the following Peertube sites.": "Muestre vistas previas de video para los siguientes sitios de Peertube." "Show video previews for the following Peertube sites.": "Muestre vistas previas de video para los siguientes sitios de Peertube.",
"Follows you": "Te sigue"
} }

View File

@ -349,5 +349,6 @@
"Unfilter words": "Mots non filtrés", "Unfilter words": "Mots non filtrés",
"Show Accounts": "Afficher les comptes", "Show Accounts": "Afficher les comptes",
"Peertube Instances": "Instances Peertube", "Peertube Instances": "Instances Peertube",
"Show video previews for the following Peertube sites.": "Afficher des aperçus vidéo pour les sites Peertube suivants." "Show video previews for the following Peertube sites.": "Afficher des aperçus vidéo pour les sites Peertube suivants.",
"Follows you": "Vous suit"
} }

View File

@ -349,5 +349,6 @@
"Unfilter words": "Focail neamhleithleacha", "Unfilter words": "Focail neamhleithleacha",
"Show Accounts": "Taispeáin Cuntais", "Show Accounts": "Taispeáin Cuntais",
"Peertube Instances": "Imeachtaí Peertube", "Peertube Instances": "Imeachtaí Peertube",
"Show video previews for the following Peertube sites.": "Taispeáin réamhamharcanna físe do na suíomhanna Peertube seo a leanas." "Show video previews for the following Peertube sites.": "Taispeáin réamhamharcanna físe do na suíomhanna Peertube seo a leanas.",
"Follows you": "Leanann tú"
} }

View File

@ -349,5 +349,6 @@
"Unfilter words": "अनफ़िल्टर शब्द", "Unfilter words": "अनफ़िल्टर शब्द",
"Show Accounts": "खाते दिखाएं", "Show Accounts": "खाते दिखाएं",
"Peertube Instances": "Peertube उदाहरण", "Peertube Instances": "Peertube उदाहरण",
"Show video previews for the following Peertube sites.": "निम्नलिखित Peertube साइटों के लिए वीडियो पूर्वावलोकन दिखाएं।" "Show video previews for the following Peertube sites.": "निम्नलिखित Peertube साइटों के लिए वीडियो पूर्वावलोकन दिखाएं।",
"Follows you": "आपका पीछा करता है"
} }

View File

@ -349,5 +349,6 @@
"Unfilter words": "Parole non filtrate", "Unfilter words": "Parole non filtrate",
"Show Accounts": "Mostra account", "Show Accounts": "Mostra account",
"Peertube Instances": "Istanze di Peertube", "Peertube Instances": "Istanze di Peertube",
"Show video previews for the following Peertube sites.": "Mostra le anteprime dei video per i seguenti siti Peertube." "Show video previews for the following Peertube sites.": "Mostra le anteprime dei video per i seguenti siti Peertube.",
"Follows you": "Ti segue"
} }

View File

@ -349,5 +349,6 @@
"Unfilter words": "単語のフィルタリングを解除する", "Unfilter words": "単語のフィルタリングを解除する",
"Show Accounts": "アカウントを表示する", "Show Accounts": "アカウントを表示する",
"Peertube Instances": "Peertubeインスタンス", "Peertube Instances": "Peertubeインスタンス",
"Show video previews for the following Peertube sites.": "次のPeertubeサイトのビデオプレビューを表示します。" "Show video previews for the following Peertube sites.": "次のPeertubeサイトのビデオプレビューを表示します。",
"Follows you": "あなたについていきます"
} }

View File

@ -345,5 +345,6 @@
"Unfilter words": "Unfilter words", "Unfilter words": "Unfilter words",
"Show Accounts": "Show Accounts", "Show Accounts": "Show Accounts",
"Peertube Instances": "Peertube Instances", "Peertube Instances": "Peertube Instances",
"Show video previews for the following Peertube sites.": "Show video previews for the following Peertube sites." "Show video previews for the following Peertube sites.": "Show video previews for the following Peertube sites.",
"Follows you": "Follows you"
} }

View File

@ -349,5 +349,6 @@
"Unfilter words": "Palavras sem filtro", "Unfilter words": "Palavras sem filtro",
"Show Accounts": "Mostrar contas", "Show Accounts": "Mostrar contas",
"Peertube Instances": "Instâncias Peertube", "Peertube Instances": "Instâncias Peertube",
"Show video previews for the following Peertube sites.": "Mostrar visualizações de vídeo para os seguintes sites Peertube." "Show video previews for the following Peertube sites.": "Mostrar visualizações de vídeo para os seguintes sites Peertube.",
"Follows you": "Segue você"
} }

View File

@ -349,5 +349,6 @@
"Unfilter words": "Не фильтровать слова", "Unfilter words": "Не фильтровать слова",
"Show Accounts": "Показать счета", "Show Accounts": "Показать счета",
"Peertube Instances": "Экземпляры Peertube", "Peertube Instances": "Экземпляры Peertube",
"Show video previews for the following Peertube sites.": "Показать превью видео для следующих сайтов Peertube." "Show video previews for the following Peertube sites.": "Показать превью видео для следующих сайтов Peertube.",
"Follows you": "Следует за вами"
} }

View File

@ -349,5 +349,6 @@
"Unfilter words": "未过滤字词", "Unfilter words": "未过滤字词",
"Show Accounts": "显示帐户", "Show Accounts": "显示帐户",
"Peertube Instances": "Peertube实例", "Peertube Instances": "Peertube实例",
"Show video previews for the following Peertube sites.": "显示以下Peertube网站的视频预览。" "Show video previews for the following Peertube sites.": "显示以下Peertube网站的视频预览。",
"Follows you": "跟着你"
} }

View File

@ -19,6 +19,16 @@ from calendar import monthrange
from followingCalendar import addPersonToCalendar from followingCalendar import addPersonToCalendar
def getLockedAccount(actorJson: {}) -> bool:
"""Returns whether the given account requires follower approval
"""
if not actorJson.get('manuallyApprovesFollowers'):
return False
if actorJson['manuallyApprovesFollowers'] is True:
return True
return False
def hasUsersPath(pathStr: str) -> bool: def hasUsersPath(pathStr: str) -> bool:
"""Whether there is a /users/ path (or equivalent) in the given string """Whether there is a /users/ path (or equivalent) in the given string
""" """
@ -328,14 +338,16 @@ def removeIdEnding(idStr: str) -> str:
def getProtocolPrefixes() -> []: def getProtocolPrefixes() -> []:
"""Returns a list of valid prefixes """Returns a list of valid prefixes
""" """
return ('https://', 'http://', 'dat://', 'i2p://', 'gnunet://', return ('https://', 'http://', 'ftp://',
'dat://', 'i2p://', 'gnunet://',
'hyper://', 'gemini://', 'gopher://') 'hyper://', 'gemini://', 'gopher://')
def getLinkPrefixes() -> []: def getLinkPrefixes() -> []:
"""Returns a list of valid web link prefixes """Returns a list of valid web link prefixes
""" """
return ('https://', 'http://', 'dat://', 'i2p://', 'gnunet://', return ('https://', 'http://', 'ftp://',
'dat://', 'i2p://', 'gnunet://',
'hyper://', 'gemini://', 'gopher://', 'briar:') 'hyper://', 'gemini://', 'gopher://', 'briar:')

View File

@ -18,6 +18,7 @@ from utils import removeHtml
from utils import getDomainFromActor from utils import getDomainFromActor
from utils import getNicknameFromActor from utils import getNicknameFromActor
from blocking import isBlocked from blocking import isBlocked
from follow import isFollowerOfPerson
from follow import isFollowingActor from follow import isFollowingActor
from followingCalendar import receivingCalendarEvents from followingCalendar import receivingCalendarEvents
from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlHeaderWithExternalStyle
@ -45,7 +46,8 @@ def htmlPersonOptions(defaultTimeline: str,
PGPfingerprint: str, PGPfingerprint: str,
emailAddress: str, emailAddress: str,
dormantMonths: int, dormantMonths: int,
backToPath: str) -> str: backToPath: str,
lockedAccount: bool) -> str:
"""Show options for a person: view/follow/block/report """Show options for a person: view/follow/block/report
""" """
optionsDomain, optionsPort = getDomainFromActor(optionsActor) optionsDomain, optionsPort = getDomainFromActor(optionsActor)
@ -61,6 +63,7 @@ def htmlPersonOptions(defaultTimeline: str,
blockStr = 'Block' blockStr = 'Block'
nickname = None nickname = None
optionsNickname = None optionsNickname = None
followsYou = False
if originPathStr.startswith('/users/'): if originPathStr.startswith('/users/'):
nickname = originPathStr.split('/users/')[1] nickname = originPathStr.split('/users/')[1]
if '/' in nickname: if '/' in nickname:
@ -76,6 +79,10 @@ def htmlPersonOptions(defaultTimeline: str,
optionsNickname = getNicknameFromActor(optionsActor) optionsNickname = getNicknameFromActor(optionsActor)
optionsDomainFull = getFullDomain(optionsDomain, optionsPort) optionsDomainFull = getFullDomain(optionsDomain, optionsPort)
followsYou = \
isFollowerOfPerson(baseDir,
nickname, domain,
optionsNickname, optionsDomainFull)
if isBlocked(baseDir, nickname, domain, if isBlocked(baseDir, nickname, domain,
optionsNickname, optionsDomainFull): optionsNickname, optionsDomainFull):
blockStr = 'Block' blockStr = 'Block'
@ -112,11 +119,16 @@ def htmlPersonOptions(defaultTimeline: str,
'" ' + getBrokenLinkSubstitute() + '/></a>\n' '" ' + getBrokenLinkSubstitute() + '/></a>\n'
handle = getNicknameFromActor(optionsActor) + '@' + optionsDomain handle = getNicknameFromActor(optionsActor) + '@' + optionsDomain
handleShown = handle handleShown = handle
if lockedAccount:
handleShown += '🔒'
if dormant: if dormant:
handleShown += ' 💤' handleShown += ' 💤'
optionsStr += \ optionsStr += \
' <p class="optionsText">' + translate['Options for'] + \ ' <p class="optionsText">' + translate['Options for'] + \
' @' + handleShown + '</p>\n' ' @' + handleShown + '</p>\n'
if followsYou:
optionsStr += \
' <p class="optionsText">' + translate['Follows you'] + '</p>\n'
if emailAddress: if emailAddress:
optionsStr += \ optionsStr += \
'<p class="imText">' + translate['Email'] + \ '<p class="imText">' + translate['Email'] + \

View File

@ -8,6 +8,7 @@ __status__ = "Production"
import os import os
from pprint import pprint from pprint import pprint
from utils import getLockedAccount
from utils import hasUsersPath from utils import hasUsersPath
from utils import getFullDomain from utils import getFullDomain
from utils import isDormant from utils import isDormant
@ -37,6 +38,7 @@ from tox import getToxAddress
from briar import getBriarAddress from briar import getBriarAddress
from jami import getJamiAddress from jami import getJamiAddress
from filters import isFiltered from filters import isFiltered
from follow import isFollowerOfPerson
from webapp_frontscreen import htmlFrontScreen from webapp_frontscreen import htmlFrontScreen
from webapp_utils import scheduledPostsExist from webapp_utils import scheduledPostsExist
from webapp_utils import getPersonAvatarUrl from webapp_utils import getPersonAvatarUrl
@ -164,6 +166,17 @@ def htmlProfileAfterSearch(cssCache: {},
displayName = searchNickname displayName = searchNickname
if profileJson.get('name'): if profileJson.get('name'):
displayName = profileJson['name'] displayName = profileJson['name']
lockedAccount = getLockedAccount(profileJson)
if lockedAccount:
displayName += '🔒'
followsYou = \
isFollowerOfPerson(baseDir,
nickname, domain,
searchNickname,
searchDomainFull)
profileDescription = '' profileDescription = ''
if profileJson.get('summary'): if profileJson.get('summary'):
profileDescription = profileJson['summary'] profileDescription = profileJson['summary']
@ -218,7 +231,7 @@ def htmlProfileAfterSearch(cssCache: {},
searchNickname, searchNickname,
searchDomainFull, searchDomainFull,
translate, translate,
displayName, displayName, followsYou,
profileDescriptionShort, profileDescriptionShort,
avatarUrl, imageUrl) avatarUrl, imageUrl)
@ -324,6 +337,7 @@ def _getProfileHeaderAfterSearch(baseDir: str,
searchDomainFull: str, searchDomainFull: str,
translate: {}, translate: {},
displayName: str, displayName: str,
followsYou: bool,
profileDescriptionShort: str, profileDescriptionShort: str,
avatarUrl: str, imageUrl: str) -> str: avatarUrl: str, imageUrl: str) -> str:
"""The header of a searched for handle, containing background """The header of a searched for handle, containing background
@ -346,6 +360,8 @@ def _getProfileHeaderAfterSearch(baseDir: str,
htmlStr += ' <h1>' + displayName + '</h1>\n' htmlStr += ' <h1>' + displayName + '</h1>\n'
htmlStr += \ htmlStr += \
' <p><b>@' + searchNickname + '@' + searchDomainFull + '</b><br>\n' ' <p><b>@' + searchNickname + '@' + searchDomainFull + '</b><br>\n'
if followsYou:
htmlStr += ' <p><b>' + translate['Follows you'] + '</b></p>\n'
htmlStr += ' <p>' + profileDescriptionShort + '</p>\n' htmlStr += ' <p>' + profileDescriptionShort + '</p>\n'
htmlStr += ' </figcaption>\n' htmlStr += ' </figcaption>\n'
htmlStr += ' </figure>\n\n' htmlStr += ' </figure>\n\n'