mirror of https://gitlab.com/bashrc2/epicyon
Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon
commit
96fe9f0513
16
daemon.py
16
daemon.py
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
39
inbox.py
39
inbox.py
|
|
@ -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
|
||||||
|
|
|
||||||
155
jsonldsig.py
155
jsonldsig.py
|
|
@ -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
|
|
||||||
|
|
@ -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")
|
||||||
8
posts.py
8
posts.py
|
|
@ -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))
|
||||||
|
|
|
||||||
523
pyjsonld.py
523
pyjsonld.py
|
|
@ -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:
|
||||||
|
|
|
||||||
71
tests.py
71
tests.py
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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": "يتبعك"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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ú"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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": "आपका पीछा करता है"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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": "あなたについていきます"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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ê"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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": "Следует за вами"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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": "跟着你"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
utils.py
16
utils.py
|
|
@ -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:')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'] + \
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue