Option to use dat urls

master
Bob Mottram 2019-07-03 20:00:03 +01:00
parent 0f5592452a
commit 86aaff3c84
11 changed files with 137 additions and 202 deletions

View File

@ -12,7 +12,7 @@ from utils import getStatusNumber
from utils import createOutboxDir
from utils import urlPermitted
def createAcceptReject(baseDir: str,federationList: [],nickname: str,domain: str,port: int,toUrl: str,ccUrl: str,https: bool,objectUrl: str,acceptType: str) -> {}:
def createAcceptReject(baseDir: str,federationList: [],nickname: str,domain: str,port: int,toUrl: str,ccUrl: str,httpPrefix: str,objectUrl: str,acceptType: str) -> {}:
"""Accepts or rejects something (eg. a follow request)
Typically toUrl will be https://www.w3.org/ns/activitystreams#Public
and ccUrl might be a specific person favorited or repeated and the followers url
@ -21,16 +21,12 @@ def createAcceptReject(baseDir: str,federationList: [],nickname: str,domain: str
if not urlPermitted(objectUrl,federationList):
return None
prefix='https'
if not https:
prefix='http'
if port!=80 and port!=443:
domain=domain+':'+str(port)
newAccept = {
'type': acceptType,
'actor': prefix+'://'+domain+'/users/'+nickname,
'actor': httpPrefix+'://'+domain+'/users/'+nickname,
'to': [toUrl],
'cc': [],
'object': objectUrl
@ -40,8 +36,8 @@ def createAcceptReject(baseDir: str,federationList: [],nickname: str,domain: str
newAccept['cc']=ccUrl
return newAccept
def createAccept(baseDir: str,federationList: [],nickname: str,domain: str,port: int,toUrl: str,ccUrl: str,https: bool,objectUrl: str) -> {}:
return createAcceptReject(baseDir,federationList,nickname,domain,port,toUrl,ccUrl,https,objectUrl,'Accept')
def createAccept(baseDir: str,federationList: [],nickname: str,domain: str,port: int,toUrl: str,ccUrl: str,httpPrefix: str,objectUrl: str) -> {}:
return createAcceptReject(baseDir,federationList,nickname,domain,port,toUrl,ccUrl,httpPrefix,objectUrl,'Accept')
def createReject(baseDir: str,federationList: [],nickname: str,domain: str,port: int,toUrl: str,ccUrl: str,https: bool,objectUrl: str) -> {}:
return createAcceptReject(baseDir,federationList,nickname,domain,port,toUrl,ccUrl,https,objectUrl,'Reject')
def createReject(baseDir: str,federationList: [],nickname: str,domain: str,port: int,toUrl: str,ccUrl: str,httpPrefix: str,objectUrl: str) -> {}:
return createAcceptReject(baseDir,federationList,nickname,domain,port,toUrl,ccUrl,httpPrefix,objectUrl,'Reject')

View File

@ -14,7 +14,7 @@ from utils import urlPermitted
def createAnnounce(baseDir: str,federationList: [], \
nickname: str, domain: str, port: int, \
toUrl: str, ccUrl: str, https: bool, \
toUrl: str, ccUrl: str, httpPrefix: str, \
objectUrl: str, saveToFile: bool) -> {}:
"""Creates an announce message
Typically toUrl will be https://www.w3.org/ns/activitystreams#Public
@ -24,18 +24,14 @@ def createAnnounce(baseDir: str,federationList: [], \
if not urlPermitted(objectUrl,federationList):
return None
prefix='https'
if not https:
prefix='http'
if port!=80 and port!=443:
domain=domain+':'+str(port)
statusNumber,published = getStatusNumber()
newAnnounceId=prefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber
newAnnounceId=httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber
newAnnounce = {
'actor': prefix+'://'+domain+'/users/'+nickname,
'atomUri': prefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber,
'actor': httpPrefix+'://'+domain+'/users/'+nickname,
'atomUri': httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber,
'cc': [],
'id': newAnnounceId+'/activity',
'object': objectUrl,
@ -56,40 +52,32 @@ def createAnnounce(baseDir: str,federationList: [], \
return newAnnounce
def announcePublic(baseDir: str,federationList: [], \
nickname: str, domain: str, port: int, https: bool, \
nickname: str, domain: str, port: int, httpPrefix: str, \
objectUrl: str, saveToFile: bool) -> {}:
"""Makes a public announcement
"""
prefix='https'
if not https:
prefix='http'
fromDomain=domain
if port!=80 and port!=443:
fromDomain=fromDomain+':'+str(port)
toUrl = 'https://www.w3.org/ns/activitystreams#Public'
ccUrl = prefix + '://'+fromDomain+'/users/'+nickname+'/followers'
ccUrl = httpPrefix + '://'+fromDomain+'/users/'+nickname+'/followers'
return createAnnounce(baseDir,nickname, domain, port, \
toUrl, ccUrl, https, objectUrl, saveToFile)
toUrl, ccUrl, httpPrefix, objectUrl, saveToFile)
def repeatPost(baseDir: str,federationList: [], \
nickname: str, domain: str, port: int, https: bool, \
nickname: str, domain: str, port: int, httpPrefix: str, \
announceNickname: str, announceDomain: str, \
announcePort: int, announceHttps: bool, \
announcePort: int, announceHttpsPrefix: str, \
announceStatusNumber: int, saveToFile: bool) -> {}:
"""Repeats a given status post
"""
prefix='https'
if not announceHttps:
prefix='http'
announcedDomain=announceDomain
if announcePort!=80 and announcePort!=443:
announcedDomain=announcedDomain+':'+str(announcePort)
objectUrl = prefix + '://'+announcedDomain+'/users/'+ \
objectUrl = announceHttpsPrefix + '://'+announcedDomain+'/users/'+ \
announceNickname+'/statuses/'+str(announceStatusNumber)
return announcePublic(baseDir,nickname, domain, port, https, objectUrl, saveToFile)
return announcePublic(baseDir,nickname, domain, port, httpPrefix, objectUrl, saveToFile)

View File

@ -129,7 +129,7 @@ class PubServer(BaseHTTPRequestHandler):
# get outbox feed for a person
outboxFeed=personOutboxJson(self.server.baseDir,self.server.domain, \
self.server.port,self.path, \
self.server.https,maxPostsInFeed)
self.server.httpPrefix,maxPostsInFeed)
if outboxFeed:
self._set_headers('application/json')
self.wfile.write(json.dumps(outboxFeed).encode('utf-8'))
@ -137,7 +137,7 @@ class PubServer(BaseHTTPRequestHandler):
return
following=getFollowingFeed(self.server.baseDir,self.server.domain, \
self.server.port,self.path, \
self.server.https,followsPerPage)
self.server.httpPrefix,followsPerPage)
if following:
self._set_headers('application/json')
self.wfile.write(json.dumps(following).encode('utf-8'))
@ -145,7 +145,7 @@ class PubServer(BaseHTTPRequestHandler):
return
followers=getFollowingFeed(self.server.baseDir,self.server.domain, \
self.server.port,self.path, \
self.server.https,followsPerPage,'followers')
self.server.httpPrefix,followsPerPage,'followers')
if followers:
self._set_headers('application/json')
self.wfile.write(json.dumps(followers).encode('utf-8'))
@ -305,7 +305,7 @@ class PubServer(BaseHTTPRequestHandler):
if self.server.debug:
print('DEBUG: POST check signature')
if not verifyPostHeaders(self.server.https, pubKey, self.headers, \
if not verifyPostHeaders(self.server.httpPrefix, pubKey, self.headers, \
'/inbox' ,False, json.dumps(messageJson)):
print('**************** POST signature verification failed')
self.send_response(401)
@ -336,7 +336,7 @@ class PubServer(BaseHTTPRequestHandler):
self.end_headers()
self.server.POSTbusy=False
def runDaemon(domain: str,port=80,https=True,fedList=[],useTor=False,debug=False) -> None:
def runDaemon(domain: str,port=80,httpPrefix='https',fedList=[],useTor=False,debug=False) -> None:
if len(domain)==0:
domain='localhost'
if '.' not in domain:
@ -348,7 +348,7 @@ def runDaemon(domain: str,port=80,https=True,fedList=[],useTor=False,debug=False
httpd = ThreadingHTTPServer(serverAddress, PubServer)
httpd.domain=domain
httpd.port=port
httpd.https=https
httpd.httpPrefix=httpPrefix
httpd.debug=debug
httpd.federationList=fedList.copy()
httpd.baseDir=os.getcwd()

View File

@ -67,6 +67,9 @@ parser.add_argument("--debug", type=str2bool, nargs='?',
parser.add_argument("--http", type=str2bool, nargs='?',
const=True, default=False,
help="Use http only")
parser.add_argument("--dat", type=str2bool, nargs='?',
const=True, default=False,
help="Use dat protocol only")
parser.add_argument("--tor", type=str2bool, nargs='?',
const=True, default=False,
help="Route via Tor")
@ -110,9 +113,11 @@ if not args.domain:
nickname='admin'
domain=args.domain
port=args.port
https=True
httpPrefix='https'
if args.http:
https=False
httpPrefix='http'
if args.dat:
httpPrefix='dat'
useTor=args.tor
baseDir=args.baseDir
if baseDir.endswith('/'):
@ -126,6 +131,6 @@ if args.federationList:
if not os.path.isdir(baseDir+'/accounts/'+nickname+'@'+domain):
print('Creating default admin account '+nickname+'@'+domain)
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,nickname,domain,port,https,True)
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,nickname,domain,port,httpPrefix,True)
runDaemon(domain,port,https,federationList,useTor,debug)
runDaemon(domain,port,httpPrefix,federationList,useTor,debug)

View File

@ -114,7 +114,7 @@ def getNoOfFollowers(baseDir: str,nickname: str,domain: str) -> int:
"""
return getNoOfFollows(baseDir,nickname,domain,'followers.txt')
def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,https: bool, \
def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,httpPrefix: str, \
followsPerPage=12,followFile='following') -> {}:
"""Returns the following and followers feeds from GET requests
"""
@ -147,18 +147,14 @@ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,https: bool, \
if not validNickname(nickname):
return None
prefix='https'
if not https:
prefix='http'
if port!=80 and port!=443:
domain=domain+':'+str(port)
if headerOnly:
following = {
'@context': 'https://www.w3.org/ns/activitystreams',
'first': prefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page=1',
'id': prefix+'://'+domain+'/users/'+nickname+'/'+followFile,
'first': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page=1',
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile,
'totalItems': getNoOfFollows(nickname,domain),
'type': 'OrderedCollection'}
return following
@ -169,9 +165,9 @@ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,https: bool, \
nextPageNumber=int(pageNumber+1)
following = {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': prefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page='+str(pageNumber),
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page='+str(pageNumber),
'orderedItems': [],
'partOf': prefix+'://'+domain+'/users/'+nickname+'/'+followFile,
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile,
'totalItems': 0,
'type': 'OrderedCollectionPage'}
@ -190,10 +186,10 @@ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,https: bool, \
pageCtr += 1
totalCtr += 1
if currPage==pageNumber:
url = prefix + '://' + line.lower().replace('\n','').split('@')[1] + \
url = httpPrefix + '://' + line.lower().replace('\n','').split('@')[1] + \
'/users/' + line.lower().replace('\n','').split('@')[0]
following['orderedItems'].append(url)
elif line.startswith('http') and '/users/' in line:
elif (line.startswith('http') or line.startswith('dat')) and '/users/' in line:
pageCtr += 1
totalCtr += 1
if currPage==pageNumber:
@ -206,7 +202,7 @@ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,https: bool, \
if lastPage<1:
lastPage=1
if nextPageNumber>lastPage:
following['next']=prefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page='+str(lastPage)
following['next']=httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page='+str(lastPage)
return following
def receiveFollowRequest(baseDir: str,messageJson: {},federationList: []) -> bool:
@ -216,7 +212,7 @@ def receiveFollowRequest(baseDir: str,messageJson: {},federationList: []) -> boo
return False
if '/users/' not in messageJson['actor']:
return False
domain=messageJson['actor'].split('/users/')[0].replace('https://','').replace('http://','')
domain=messageJson['actor'].split('/users/')[0].replace('https://','').replace('http://','').replace('dat://','')
if not domainPermitted(domain,federationList):
return False
nickname=messageJson['actor'].split('/users/')[1].replace('@','')
@ -225,7 +221,7 @@ def receiveFollowRequest(baseDir: str,messageJson: {},federationList: []) -> boo
return False
if '/users/' not in messageJson['object']:
return False
domainToFollow=messageJson['object'].split('/users/')[0].replace('https://','').replace('http://','')
domainToFollow=messageJson['object'].split('/users/')[0].replace('https://','').replace('http://','').replace('dat://','')
if not domainPermitted(domainToFollow,federationList):
return False
nicknameToFollow=messageJson['object'].split('/users/')[1].replace('@','')
@ -235,22 +231,14 @@ def receiveFollowRequest(baseDir: str,messageJson: {},federationList: []) -> boo
return False
return followerOfPerson(baseDir,nickname,domain,nicknameToFollow,domainToFollow,federationList)
def sendFollowRequest(baseDir: str,nickname: str,domain: str,port: int,https: bool, \
followNickname: str,followDomain: str,followPort: bool,followHttps: bool, \
def sendFollowRequest(baseDir: str,nickname: str,domain: str,port: int,httpPrefix: str, \
followNickname: str,followDomain: str,followPort: bool,followHttpPrefix: str, \
federationList: []) -> {}:
"""Gets the json object for sending a follow request
"""
if not domainPermitted(followDomain,federationList):
return None
prefix='https'
if not https:
prefix='http'
followPrefix='https'
if not followHttps:
followPrefix='http'
if port!=80 and port!=443:
domain=domain+':'+str(port)
@ -259,8 +247,8 @@ def sendFollowRequest(baseDir: str,nickname: str,domain: str,port: int,https: bo
newFollow = {
'type': 'Follow',
'actor': prefix+'://'+domain+'/users/'+nickname,
'object': followPrefix+'://'+followDomain+'/users/'+followNickname,
'actor': httpPrefix+'://'+domain+'/users/'+nickname,
'object': followHttpPrefix+'://'+followDomain+'/users/'+followNickname,
'to': [toUrl],
'cc': []
}

View File

@ -17,18 +17,14 @@ import json
def signPostHeaders(privateKeyPem: str, nickname: str, domain: str, \
port: int,path: str, \
https: bool, messageBodyJson: {}) -> str:
httpPrefix: str, messageBodyJson: {}) -> str:
"""Returns a raw signature string that can be plugged into a header and
used to verify the authenticity of an HTTP transmission.
"""
prefix='https'
if not https:
prefix='http'
if port!=80 and port!=443:
domain=domain+':'+str(port)
keyID = prefix+'://'+domain+'/users/'+nickname+'/main-key'
keyID = httpPrefix+'://'+domain+'/users/'+nickname+'/main-key'
if not messageBodyJson:
headers = {'host': domain}
else:
@ -63,7 +59,7 @@ def signPostHeaders(privateKeyPem: str, nickname: str, domain: str, \
return signatureHeader
def createSignedHeader(privateKeyPem: str,nickname: str,domain: str,port: int, \
path: str,https: bool,withDigest: bool, \
path: str,httpPrefix: str,withDigest: bool, \
messageBodyJson: {}) -> {}:
headerDomain=domain
@ -79,12 +75,12 @@ def createSignedHeader(privateKeyPem: str,nickname: str,domain: str,port: int, \
headers = {'host': headerDomain, 'digest': f'SHA-256={bodyDigest}'}
path='/inbox'
signatureHeader = signPostHeaders(privateKeyPem, nickname, domain, port, \
path, https, None)
path, httpPrefix, None)
headers['signature'] = signatureHeader
headers['Content-type'] = 'application/json'
return headers
def verifyPostHeaders(https: bool, publicKeyPem: str, headers: dict, \
def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict, \
path: str, GETmethod: bool, \
messageBodyJsonStr: str) -> bool:
"""Returns true or false depending on if the key that we plugged in here
@ -100,10 +96,6 @@ def verifyPostHeaders(https: bool, publicKeyPem: str, headers: dict, \
else:
method='POST'
prefix='https'
if not https:
prefix='http'
publicKeyPem = RSA.import_key(publicKeyPem)
# Build a dictionary of the signature values
signatureHeader = headers['signature']

18
like.py
View File

@ -11,7 +11,7 @@ import commentjson
from utils import urlPermitted
def like(baseDir: str,federationList: [],nickname: str,domain: str,port: int, \
toUrl: str,ccUrl: str,https: bool,objectUrl: str,saveToFile: bool) -> {}:
toUrl: str,ccUrl: str,httpPrefix: str,objectUrl: str,saveToFile: bool) -> {}:
"""Creates a like
Typically toUrl will be a followers collection
and ccUrl might be a specific person whose post was liked
@ -20,16 +20,12 @@ def like(baseDir: str,federationList: [],nickname: str,domain: str,port: int, \
if not urlPermitted(objectUrl,federationList):
return None
prefix='https'
if not https:
prefix='http'
if port!=80 and port!=443:
domain=domain+':'+str(port)
newLike = {
'type': 'Like',
'actor': prefix+'://'+domain+'/users/'+nickname,
'actor': httpPrefix+'://'+domain+'/users/'+nickname,
'object': objectUrl,
'to': [toUrl],
'cc': []
@ -44,19 +40,15 @@ def like(baseDir: str,federationList: [],nickname: str,domain: str,port: int, \
return newLike
def likePost(baseDir: str,federationList: [], \
nickname: str, domain: str, port: int, https: bool, \n
nickname: str, domain: str, port: int, httpPrefix: str, \n
likeNickname: str, likeDomain: str, likePort: int, likeHttps: bool, \n
likeStatusNumber: int,saveToFile: bool) -> {}:
"""Likes a given status post
"""
prefix='https'
if not likeHttps:
prefix='http'
likeDomain=likeDomain
if likePort!=80 and likePort!=443:
likeDomain=likeDomain+':'+str(likePort)
objectUrl = prefix + '://'+likeDomain+'/users/'+likeNickname+'/statuses/'+str(likeStatusNumber)
objectUrl = httpPrefix + '://'+likeDomain+'/users/'+likeNickname+'/statuses/'+str(likeStatusNumber)
return like(baseDir,federationList,nickname,domain,port,toUrl,ccUrl,https,objectUrl,saveToFile)
return like(baseDir,federationList,nickname,domain,port,toUrl,ccUrl,httpPrefix,objectUrl,saveToFile)

View File

@ -22,16 +22,12 @@ def generateRSAKey() -> (str,str):
return privateKeyPem,publicKeyPem
def createPerson(baseDir: str,nickname: str,domain: str,port: int, \
https: bool, saveToFile: bool) -> (str,str,{},{}):
httpPrefix: str, saveToFile: bool) -> (str,str,{},{}):
"""Returns the private key, public key, actor and webfinger endpoint
"""
prefix='https'
if not https:
prefix='http'
privateKeyPem,publicKeyPem=generateRSAKey()
webfingerEndpoint= \
createWebfingerEndpoint(nickname,domain,port,https,publicKeyPem)
createWebfingerEndpoint(nickname,domain,port,httpPrefix,publicKeyPem)
if saveToFile:
storeWebfingerEndpoint(nickname,domain,baseDir,webfingerEndpoint)
@ -54,29 +50,29 @@ def createPerson(baseDir: str,nickname: str,domain: str,port: int, \
'toot': 'http://joinmastodon.org/ns#',
'value': 'schema:value'}],
'attachment': [],
'endpoints': {'sharedInbox': prefix+'://'+domain+'/inbox'},
'featured': prefix+'://'+domain+'/users/'+nickname+'/collections/featured',
'followers': prefix+'://'+domain+'/users/'+nickname+'/followers',
'following': prefix+'://'+domain+'/users/'+nickname+'/following',
'endpoints': {'sharedInbox': httpPrefix+'://'+domain+'/inbox'},
'featured': httpPrefix+'://'+domain+'/users/'+nickname+'/collections/featured',
'followers': httpPrefix+'://'+domain+'/users/'+nickname+'/followers',
'following': httpPrefix+'://'+domain+'/users/'+nickname+'/following',
'icon': {'mediaType': 'image/png',
'type': 'Image',
'url': prefix+'://'+domain+'/users/'+nickname+'_icon.png'},
'id': prefix+'://'+domain+'/users/'+nickname,
'url': httpPrefix+'://'+domain+'/users/'+nickname+'_icon.png'},
'id': httpPrefix+'://'+domain+'/users/'+nickname,
'image': {'mediaType': 'image/png',
'type': 'Image',
'url': prefix+'://'+domain+'/users/'+nickname+'.png'},
'inbox': prefix+'://'+domain+'/users/'+nickname+'/inbox',
'url': httpPrefix+'://'+domain+'/users/'+nickname+'.png'},
'inbox': httpPrefix+'://'+domain+'/users/'+nickname+'/inbox',
'manuallyApprovesFollowers': False,
'name': nickname,
'outbox': prefix+'://'+domain+'/users/'+nickname+'/outbox',
'outbox': httpPrefix+'://'+domain+'/users/'+nickname+'/outbox',
'preferredUsername': ''+nickname,
'publicKey': {'id': prefix+'://'+domain+'/users/'+nickname+'/main-key',
'owner': prefix+'://'+domain+'/users/'+nickname,
'publicKey': {'id': httpPrefix+'://'+domain+'/users/'+nickname+'/main-key',
'owner': httpPrefix+'://'+domain+'/users/'+nickname,
'publicKeyPem': publicKeyPem,
'summary': '',
'tag': [],
'type': 'Person',
'url': prefix+'://'+domain+'/@'+nickname}
'url': httpPrefix+'://'+domain+'/@'+nickname}
}
if saveToFile:
@ -170,7 +166,7 @@ def personLookup(domain: str,path: str,baseDir: str) -> {}:
return personJson
def personOutboxJson(baseDir: str,domain: str,port: int,path: str, \
https: bool,noOfItems: int) -> []:
httpPrefix: str,noOfItems: int) -> []:
"""Obtain the outbox feed for the given person
"""
if not '/outbox' in path:
@ -204,7 +200,7 @@ def personOutboxJson(baseDir: str,domain: str,port: int,path: str, \
return None
if not validNickname(nickname):
return None
return createOutbox(baseDir,nickname,domain,port,https, \
return createOutbox(baseDir,nickname,domain,port,httpPrefix, \
noOfItems,headerOnly,pageNumber)
def setPreferredNickname(baseDir: str,nickname: str, domain: str, \

View File

@ -250,15 +250,11 @@ def deleteAllPosts(baseDir: str,nickname: str, domain: str) -> None:
print(e)
def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
toUrl: str, ccUrl: str, https: bool, content: str, \
toUrl: str, ccUrl: str, httpPrefix: str, content: str, \
followersOnly: bool, saveToFile: bool, clientToServer: bool, \
inReplyTo=None, inReplyToAtomUri=None, subject=None) -> {}:
"""Creates a message
"""
prefix='https'
if not https:
prefix='http'
if port!=80 and port!=443:
domain=domain+':'+str(port)
@ -266,11 +262,11 @@ def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
conversationDate=published.split('T')[0]
conversationId=statusNumber
postTo='https://www.w3.org/ns/activitystreams#Public'
postCC=prefix+'://'+domain+'/users/'+nickname+'/followers'
postCC=httpPrefix+'://'+domain+'/users/'+nickname+'/followers'
if followersOnly:
postTo=postCC
postCC=''
newPostId=prefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber
newPostId=httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber
sensitive=False
summary=None
if subject:
@ -280,7 +276,7 @@ def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
newPost = {
'id': newPostId+'/activity',
'type': 'Create',
'actor': prefix+'://'+domain+'/users/'+nickname,
'actor': httpPrefix+'://'+domain+'/users/'+nickname,
'published': published,
'to': [toUrl],
'cc': [],
@ -290,12 +286,12 @@ def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
'summary': summary,
'inReplyTo': inReplyTo,
'published': published,
'url': prefix+'://'+domain+'/@'+nickname+'/'+statusNumber,
'attributedTo': prefix+'://'+domain+'/users/'+nickname,
'url': httpPrefix+'://'+domain+'/@'+nickname+'/'+statusNumber,
'attributedTo': httpPrefix+'://'+domain+'/users/'+nickname,
'to': [toUrl],
'cc': [],
'sensitive': sensitive,
'atomUri': prefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber,
'atomUri': httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber,
'inReplyToAtomUri': inReplyToAtomUri,
'conversation': 'tag:'+domain+','+conversationDate+':objectId='+conversationId+':objectType=Conversation',
'content': content,
@ -322,12 +318,12 @@ def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
'summary': summary,
'inReplyTo': inReplyTo,
'published': published,
'url': prefix+'://'+domain+'/@'+nickname+'/'+statusNumber,
'attributedTo': prefix+'://'+domain+'/users/'+nickname,
'url': httpPrefix+'://'+domain+'/@'+nickname+'/'+statusNumber,
'attributedTo': httpPrefix+'://'+domain+'/users/'+nickname,
'to': [toUrl],
'cc': [],
'sensitive': sensitive,
'atomUri': prefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber,
'atomUri': httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber,
'inReplyToAtomUri': inReplyToAtomUri,
'conversation': 'tag:'+domain+','+conversationDate+':objectId='+conversationId+':objectType=Conversation',
'content': content,
@ -352,19 +348,16 @@ def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
return newPost
def createPublicPost(baseDir: str,
nickname: str, domain: str, port: int,https: bool, \
nickname: str, domain: str, port: int,httpPrefix: str, \
content: str, followersOnly: bool, saveToFile: bool,
clientToServer: bool, \
inReplyTo=None, inReplyToAtomUri=None, subject=None) -> {}:
"""Public post to the outbox
"""
prefix='https'
if not https:
prefix='http'
return createPostBase(baseDir,nickname, domain, port, \
'https://www.w3.org/ns/activitystreams#Public', \
prefix+'://'+domain+'/users/'+nickname+'/followers', \
https, content, followersOnly, saveToFile, clientToServer, \
httpPrefix+'://'+domain+'/users/'+nickname+'/followers', \
httpPrefix, content, followersOnly, saveToFile, clientToServer, \
inReplyTo, inReplyToAtomUri, subject)
def threadSendPost(session,postJsonObject: {},federationList: [],inboxUrl: str, \
@ -394,25 +387,21 @@ def threadSendPost(session,postJsonObject: {},federationList: [],inboxUrl: str,
def sendPost(session,baseDir: str,nickname: str, domain: str, port: int, \
toNickname: str, toDomain: str, toPort: int, cc: str, \
https: bool, content: str, followersOnly: bool, \
httpPrefix: str, content: str, followersOnly: bool, \
saveToFile: bool, clientToServer: bool, federationList: [], \
sendThreads: [], postLog: [], cachedWebfingers: {},personCache: {}, \
inReplyTo=None, inReplyToAtomUri=None, subject=None) -> int:
"""Post to another inbox
"""
prefix='https'
if not https:
prefix='http'
withDigest=True
if toPort!=80 and toPort!=443:
toDomain=toDomain+':'+str(toPort)
handle=prefix+'://'+toDomain+'/@'+toNickname
handle=httpPrefix+'://'+toDomain+'/@'+toNickname
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,https,cachedWebfingers)
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers)
if not wfRequest:
return 1
@ -428,7 +417,7 @@ def sendPost(session,baseDir: str,nickname: str, domain: str, port: int, \
postJsonObject = \
createPostBase(baseDir,nickname,domain,port, \
toPersonId,cc,https,content, \
toPersonId,cc,httpPrefix,content, \
followersOnly,saveToFile,clientToServer, \
inReplyTo,inReplyToAtomUri, \
subject)
@ -446,7 +435,7 @@ def sendPost(session,baseDir: str,nickname: str, domain: str, port: int, \
# construct the http header
signatureHeaderJson = \
createSignedHeader(privateKeyPem, nickname, domain, port, \
postPath, https, withDigest, postJsonObject)
postPath, httpPrefix, withDigest, postJsonObject)
# Keep the number of threads being used small
while len(sendThreads)>10:
@ -462,14 +451,10 @@ def sendPost(session,baseDir: str,nickname: str, domain: str, port: int, \
thr.start()
return 0
def createOutbox(baseDir: str,nickname: str,domain: str,port: int,https: bool, \
def createOutbox(baseDir: str,nickname: str,domain: str,port: int,httpPrefix: str, \
itemsPerPage: int,headerOnly: bool,pageNumber=None) -> {}:
"""Constructs the outbox feed
"""
prefix='https'
if not https:
prefix='http'
outboxDir = createOutboxDir(nickname,domain,baseDir)
if port!=80 and port!=443:
@ -482,16 +467,16 @@ def createOutbox(baseDir: str,nickname: str,domain: str,port: int,https: bool, \
except:
pass
outboxHeader = {'@context': 'https://www.w3.org/ns/activitystreams',
'first': prefix+'://'+domain+'/users/'+nickname+'/outbox?page=true',
'id': prefix+'://'+domain+'/users/'+nickname+'/outbox',
'last': prefix+'://'+domain+'/users/'+nickname+'/outbox?page=true',
'first': httpPrefix+'://'+domain+'/users/'+nickname+'/outbox?page=true',
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/outbox',
'last': httpPrefix+'://'+domain+'/users/'+nickname+'/outbox?page=true',
'totalItems': 0,
'type': 'OrderedCollection'}
outboxItems = {'@context': 'https://www.w3.org/ns/activitystreams',
'id': prefix+'://'+domain+'/users/'+nickname+'/outbox'+pageStr,
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/outbox'+pageStr,
'orderedItems': [
],
'partOf': prefix+'://'+domain+'/users/'+nickname+'/outbox',
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/outbox',
'type': 'OrderedCollectionPage'}
# counter for posts loop
@ -513,7 +498,7 @@ def createOutbox(baseDir: str,nickname: str,domain: str,port: int,https: bool, \
if lastPage<1:
lastPage=1
outboxHeader['last']= \
prefix+'://'+domain+'/users/'+nickname+'/outbox?page='+str(lastPage)
httpPrefix+'://'+domain+'/users/'+nickname+'/outbox?page='+str(lastPage)
# Insert posts
currPage=1
@ -524,7 +509,7 @@ def createOutbox(baseDir: str,nickname: str,domain: str,port: int,https: bool, \
# update the prev entry for the last message id
postId = prevPostFilename.split('#statuses#')[1].replace('#activity','')
outboxHeader['prev']= \
prefix+'://'+domain+'/users/'+nickname+'/outbox?min_id='+postId+'&page=true'
httpPrefix+'://'+domain+'/users/'+nickname+'/outbox?min_id='+postId+'&page=true'
# get the full path of the post file
filePath = os.path.join(outboxDir, postFilename)
try:
@ -542,7 +527,7 @@ def createOutbox(baseDir: str,nickname: str,domain: str,port: int,https: bool, \
if '/statuses/' in p['id']:
postId = p['id'].split('/statuses/')[1].replace('/activity','')
outboxHeader['next']= \
prefix+'://'+domain+'/users/'+ \
httpPrefix+'://'+domain+'/users/'+ \
nickname+'/outbox?max_id='+ \
postId+'&page=true'
postsOnPageCtr += 1

View File

@ -43,10 +43,10 @@ def testHttpsigBase(withDigest):
print('testHttpsig(' + str(withDigest) + ')')
nickname='socrates'
domain='argumentative.social'
https=True
httpPrefix='https'
port=5576
baseDir=os.getcwd()
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,nickname,domain,port,https,False)
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,nickname,domain,port,httpPrefix,False)
messageBodyJsonStr = '{"a key": "a value", "another key": "A string"}'
headersDomain=domain
@ -60,11 +60,11 @@ def testHttpsigBase(withDigest):
headers = {'host': headersDomain, 'digest': f'SHA-256={bodyDigest}'}
path='/inbox'
signatureHeader = signPostHeaders(privateKeyPem, nickname, domain, port, path, https, None)
signatureHeader = signPostHeaders(privateKeyPem, nickname, domain, port, path, httpPrefix, None)
headers['signature'] = signatureHeader
assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox' ,False, messageBodyJsonStr)
assert verifyPostHeaders(https, publicKeyPem, headers, '/parambulator/inbox', False , messageBodyJsonStr) == False
assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox', True, messageBodyJsonStr) == False
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, '/inbox' ,False, messageBodyJsonStr)
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, '/parambulator/inbox', False , messageBodyJsonStr) == False
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, '/inbox', True, messageBodyJsonStr) == False
if not withDigest:
# fake domain
headers = {'host': 'bogon.domain'}
@ -74,7 +74,7 @@ def testHttpsigBase(withDigest):
bodyDigest = base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest())
headers = {'host': domain, 'digest': f'SHA-256={bodyDigest}'}
headers['signature'] = signatureHeader
assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox', True, messageBodyJsonStr) == False
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, '/inbox', True, messageBodyJsonStr) == False
def testHttpsig():
testHttpsigBase(False)
@ -111,20 +111,20 @@ def createServerAlice(path: str,domain: str,port: int,federationList: []):
os.mkdir(path)
os.chdir(path)
nickname='alice'
https=False
httpPrefix=False
useTor=False
clientToServer=False
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(path,nickname,domain,port,https,True)
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(path,nickname,domain,port,httpPrefix,True)
deleteAllPosts(path,nickname,domain)
followPerson(path,nickname,domain,'bob','127.0.0.100:61936',federationList)
followerOfPerson(path,nickname,domain,'bob','127.0.0.100:61936',federationList)
createPublicPost(path,nickname, domain, port,https, "No wise fish would go anywhere without a porpoise", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,https, "Curiouser and curiouser!", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,https, "In the gardens of memory, in the palace of dreams, that is where you and I shall meet", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,httpPrefix, "No wise fish would go anywhere without a porpoise", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,httpPrefix, "Curiouser and curiouser!", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,httpPrefix, "In the gardens of memory, in the palace of dreams, that is where you and I shall meet", False, True, clientToServer)
global testServerAliceRunning
testServerAliceRunning = True
print('Server running: Alice')
runDaemon(domain,port,https,federationList,useTor,True)
runDaemon(domain,port,httpPrefix,federationList,useTor,True)
def createServerBob(path: str,domain: str,port: int,federationList: []):
print('Creating test server: Bob on port '+str(port))
@ -133,20 +133,20 @@ def createServerBob(path: str,domain: str,port: int,federationList: []):
os.mkdir(path)
os.chdir(path)
nickname='bob'
https=False
httpPrefix='http'
useTor=False
clientToServer=False
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(path,nickname,domain,port,https,True)
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(path,nickname,domain,port,httpPrefix,True)
deleteAllPosts(path,nickname,domain)
followPerson(path,nickname,domain,'alice','127.0.0.50:61935',federationList)
followerOfPerson(path,nickname,domain,'alice','127.0.0.50:61935',federationList)
createPublicPost(path,nickname, domain, port,https, "It's your life, live it your way.", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,https, "One of the things I've realised is that I am very simple", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,https, "Quantum physics is a bit of a passion of mine", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,httpPrefix, "It's your life, live it your way.", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,httpPrefix, "One of the things I've realised is that I am very simple", False, True, clientToServer)
createPublicPost(path,nickname, domain, port,httpPrefix, "Quantum physics is a bit of a passion of mine", False, True, clientToServer)
global testServerBobRunning
testServerBobRunning = True
print('Server running: Bob')
runDaemon(domain,port,https,federationList,useTor,True)
runDaemon(domain,port,httpPrefix,federationList,useTor,True)
def testPostMessageBetweenServers():
print('Testing sending message from one server to the inbox of another')
@ -156,7 +156,7 @@ def testPostMessageBetweenServers():
testServerAliceRunning = False
testServerBobRunning = False
https=False
httpPrefix='http'
useTor=False
federationList=['127.0.0.50','127.0.0.100']
@ -200,7 +200,7 @@ def testPostMessageBetweenServers():
ccUrl=None
alicePersonCache={}
aliceCachedWebfingers={}
sendResult = sendPost(sessionAlice,aliceDir,'alice', aliceDomain, alicePort, 'bob', bobDomain, bobPort, ccUrl, https, 'Why is a mouse when it spins?', followersOnly, saveToFile, clientToServer, federationList, aliceSendThreads, alicePostLog, aliceCachedWebfingers,alicePersonCache,inReplyTo, inReplyToAtomUri, subject)
sendResult = sendPost(sessionAlice,aliceDir,'alice', aliceDomain, alicePort, 'bob', bobDomain, bobPort, ccUrl, httpPrefix, 'Why is a mouse when it spins?', followersOnly, saveToFile, clientToServer, federationList, aliceSendThreads, alicePostLog, aliceCachedWebfingers,alicePersonCache,inReplyTo, inReplyToAtomUri, subject)
print('sendResult: '+str(sendResult))
for i in range(10):
@ -221,14 +221,14 @@ def testFollows():
nickname='test529'
domain='testdomain.com'
port=80
https=True
httpPrefix='https'
federationList=['wild.com','mesh.com']
baseDir=currDir+'/.tests_testfollows'
if os.path.isdir(baseDir):
shutil.rmtree(baseDir)
os.mkdir(baseDir)
os.chdir(baseDir)
createPerson(baseDir,nickname,domain,port,https,True)
createPerson(baseDir,nickname,domain,port,httpPrefix,True)
clearFollows(baseDir,nickname,domain)
followPerson(baseDir,nickname,domain,'badger','wild.com',federationList)
@ -280,7 +280,7 @@ def testCreatePerson():
nickname='test382'
domain='badgerdomain.com'
port=80
https=True
httpPrefix='https'
clientToServer=False
baseDir=currDir+'/.tests_createperson'
if os.path.isdir(baseDir):
@ -288,12 +288,12 @@ def testCreatePerson():
os.mkdir(baseDir)
os.chdir(baseDir)
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,nickname,domain,port,https,True)
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,nickname,domain,port,httpPrefix,True)
deleteAllPosts(baseDir,nickname,domain)
setPreferredNickname(baseDir,nickname,domain,'badger')
setBio(baseDir,nickname,domain,'Randomly roaming in your backyard')
archivePosts(nickname,domain,baseDir,4)
createPublicPost(baseDir,nickname, domain, port,https, "G'day world!", False, True, clientToServer, None, None, 'Not suitable for Vogons')
createPublicPost(baseDir,nickname, domain, port,httpPrefix, "G'day world!", False, True, clientToServer, None, None, 'Not suitable for Vogons')
os.chdir(currDir)
shutil.rmtree(baseDir)

View File

@ -22,11 +22,11 @@ def parseHandle(handle: str) -> (str,str):
return None, None
if '/@' in handle:
domain, nickname = \
handle.replace('https://','').replace('http://','').split('/@')
handle.replace('https://','').replace('http://','').replace('dat://','').split('/@')
else:
if '/users/' in handle:
domain, nickname = \
handle.replace('https://','').replace('http://','').split('/users/')
handle.replace('https://','').replace('http://','').replace('dat://','').split('/users/')
else:
if '@' in handle:
nickname, domain = handle.split('@')
@ -36,7 +36,7 @@ def parseHandle(handle: str) -> (str,str):
return nickname, domain
def webfingerHandle(session,handle: str,https: bool,cachedWebfingers: {}) -> {}:
def webfingerHandle(session,handle: str,httpPrefix: str,cachedWebfingers: {}) -> {}:
nickname, domain = parseHandle(handle)
if not nickname:
return None
@ -47,10 +47,7 @@ def webfingerHandle(session,handle: str,https: bool,cachedWebfingers: {}) -> {}:
wf=getWebfingerFromCache(nickname+'@'+wfDomain,cachedWebfingers)
if wf:
return wf
prefix='https'
if not https:
prefix='http'
url = '{}://{}/.well-known/webfinger'.format(prefix,domain)
url = '{}://{}/.well-known/webfinger'.format(httpPrefix,domain)
par = {'resource': 'acct:{}'.format(nickname+'@'+wfDomain)}
hdr = {'Accept': 'application/jrd+json'}
#try:
@ -83,39 +80,35 @@ def storeWebfingerEndpoint(nickname: str,domain: str,baseDir: str, \
return True
def createWebfingerEndpoint(nickname: str,domain: str,port: int, \
https: bool,publicKeyPem) -> {}:
httpPrefix: str,publicKeyPem) -> {}:
"""Creates a webfinger endpoint for a user
"""
prefix='https'
if not https:
prefix='http'
if port!=80 and port!=443:
domain=domain+':'+str(port)
account = {
"aliases": [
prefix+"://"+domain+"/@"+nickname,
prefix+"://"+domain+"/users/"+nickname
httpPrefix+"://"+domain+"/@"+nickname,
httpPrefix+"://"+domain+"/users/"+nickname
],
"links": [
{
"href": prefix+"://"+domain+"/@"+nickname,
"href": httpPrefix+"://"+domain+"/@"+nickname,
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html"
},
{
"href": prefix+"://"+domain+"/users/"+nickname+".atom",
"href": httpPrefix+"://"+domain+"/users/"+nickname+".atom",
"rel": "http://schemas.google.com/g/2010#updates-from",
"type": "application/atom+xml"
},
{
"href": prefix+"://"+domain+"/users/"+nickname,
"href": httpPrefix+"://"+domain+"/users/"+nickname,
"rel": "self",
"type": "application/activity+json"
},
{
"href": prefix+"://"+domain+"/api/salmon/1",
"href": httpPrefix+"://"+domain+"/api/salmon/1",
"rel": "salmon"
},
{
@ -124,7 +117,7 @@ def createWebfingerEndpoint(nickname: str,domain: str,port: int, \
},
{
"rel": "http://ostatus.org/schema/1.0/subscribe",
"template": prefix+"://"+domain+"/authorize_interaction?uri={uri}"
"template": httpPrefix+"://"+domain+"/authorize_interaction?uri={uri}"
}
],
"subject": "acct:"+nickname+"@"+domain