| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  | __filename__ = "webfinger.py" | 
					
						
							|  |  |  |  | __author__ = "Bob Mottram" | 
					
						
							|  |  |  |  | __license__ = "AGPL3+" | 
					
						
							| 
									
										
										
										
											2019-08-29 13:35:29 +00:00
										 |  |  |  | __version__ = "1.0.0" | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  | __maintainer__ = "Bob Mottram" | 
					
						
							|  |  |  |  | __email__ = "bob@freedombone.net" | 
					
						
							|  |  |  |  | __status__ = "Production" | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | import base64 | 
					
						
							|  |  |  |  | from Crypto.PublicKey import RSA | 
					
						
							|  |  |  |  | from Crypto.Util import number | 
					
						
							|  |  |  |  | import requests | 
					
						
							|  |  |  |  | import json | 
					
						
							|  |  |  |  | import commentjson | 
					
						
							|  |  |  |  | import os | 
					
						
							|  |  |  |  | from session import getJson | 
					
						
							| 
									
										
										
										
											2019-06-30 15:03:26 +00:00
										 |  |  |  | from cache import storeWebfingerInCache | 
					
						
							|  |  |  |  | from cache import getWebfingerFromCache | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-01 11:09:09 +00:00
										 |  |  |  | def parseHandle(handle: str) -> (str,str): | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     if '.' not in handle: | 
					
						
							|  |  |  |  |         return None, None | 
					
						
							|  |  |  |  |     if '/@' in handle: | 
					
						
							| 
									
										
										
										
											2019-07-03 09:40:27 +00:00
										 |  |  |  |         domain, nickname = \ | 
					
						
							| 
									
										
										
										
											2019-07-03 19:00:03 +00:00
										 |  |  |  |             handle.replace('https://','').replace('http://','').replace('dat://','').split('/@') | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     else: | 
					
						
							|  |  |  |  |         if '/users/' in handle: | 
					
						
							| 
									
										
										
										
											2019-07-03 09:40:27 +00:00
										 |  |  |  |             domain, nickname = \ | 
					
						
							| 
									
										
										
										
											2019-07-03 19:00:03 +00:00
										 |  |  |  |                 handle.replace('https://','').replace('http://','').replace('dat://','').split('/users/') | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |         else: | 
					
						
							|  |  |  |  |             if '@' in handle: | 
					
						
							| 
									
										
										
										
											2019-07-03 09:40:27 +00:00
										 |  |  |  |                 nickname, domain = handle.split('@') | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |             else: | 
					
						
							|  |  |  |  |                 return None, None | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-03 09:40:27 +00:00
										 |  |  |  |     return nickname, domain | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-14 20:12:27 +00:00
										 |  |  |  | def webfingerHandle(session,handle: str,httpPrefix: str,cachedWebfingers: {}, \ | 
					
						
							|  |  |  |  |                     fromDomain: str,projectVersion: str) -> {}: | 
					
						
							| 
									
										
										
										
											2019-07-16 10:19:04 +00:00
										 |  |  |  |     if not session: | 
					
						
							|  |  |  |  |         print('WARN: No session specified for webfingerHandle') | 
					
						
							|  |  |  |  |         return None | 
					
						
							| 
									
										
										
										
											2019-07-19 13:32:58 +00:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-03 09:40:27 +00:00
										 |  |  |  |     nickname, domain = parseHandle(handle) | 
					
						
							|  |  |  |  |     if not nickname: | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |         return None | 
					
						
							| 
									
										
										
										
											2019-07-01 21:01:43 +00:00
										 |  |  |  |     wfDomain=domain | 
					
						
							|  |  |  |  |     if ':' in wfDomain: | 
					
						
							| 
									
										
										
										
											2019-07-19 14:41:32 +00:00
										 |  |  |  |         #wfPort=int(wfDomain.split(':')[1]) | 
					
						
							|  |  |  |  |         #if wfPort==80 or wfPort==443: | 
					
						
							|  |  |  |  |         wfDomain=wfDomain.split(':')[0] | 
					
						
							| 
									
										
										
										
											2019-07-03 09:40:27 +00:00
										 |  |  |  |     wf=getWebfingerFromCache(nickname+'@'+wfDomain,cachedWebfingers) | 
					
						
							| 
									
										
										
										
											2019-06-30 15:03:26 +00:00
										 |  |  |  |     if wf: | 
					
						
							|  |  |  |  |         return wf | 
					
						
							| 
									
										
										
										
											2019-07-03 19:00:03 +00:00
										 |  |  |  |     url = '{}://{}/.well-known/webfinger'.format(httpPrefix,domain) | 
					
						
							| 
									
										
										
										
											2019-07-03 09:40:27 +00:00
										 |  |  |  |     par = {'resource': 'acct:{}'.format(nickname+'@'+wfDomain)} | 
					
						
							| 
									
										
										
										
											2019-07-19 16:11:09 +00:00
										 |  |  |  |     hdr = {'Accept': 'application/jrd+json'} | 
					
						
							| 
									
										
										
										
											2019-07-04 17:31:41 +00:00
										 |  |  |  |     try: | 
					
						
							| 
									
										
										
										
											2019-08-14 20:12:27 +00:00
										 |  |  |  |         result = getJson(session, url, hdr, par,projectVersion,httpPrefix,fromDomain) | 
					
						
							| 
									
										
										
										
											2019-08-14 22:24:51 +00:00
										 |  |  |  |     except Exception as e: | 
					
						
							| 
									
										
										
										
											2019-07-19 13:32:58 +00:00
										 |  |  |  |         print("Unable to webfinger " + url) | 
					
						
							|  |  |  |  |         print('headers: '+str(hdr)) | 
					
						
							|  |  |  |  |         print('params: '+str(par)) | 
					
						
							| 
									
										
										
										
											2019-08-14 22:24:51 +00:00
										 |  |  |  |         print(e) | 
					
						
							| 
									
										
										
										
											2019-07-04 17:31:41 +00:00
										 |  |  |  |         return None | 
					
						
							| 
									
										
										
										
											2019-07-03 09:40:27 +00:00
										 |  |  |  |     storeWebfingerInCache(nickname+'@'+wfDomain,result,cachedWebfingers) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     return result | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-01 11:09:09 +00:00
										 |  |  |  | def generateMagicKey(publicKeyPem) -> str: | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     """See magic_key method in
 | 
					
						
							|  |  |  |  |        https://github.com/tootsuite/mastodon/blob/707ddf7808f90e3ab042d7642d368c2ce8e95e6f/app/models/account.rb | 
					
						
							|  |  |  |  |     """
 | 
					
						
							|  |  |  |  |     privkey = RSA.importKey(publicKeyPem)     | 
					
						
							|  |  |  |  |     mod = base64.urlsafe_b64encode(number.long_to_bytes(privkey.n)).decode("utf-8") | 
					
						
							|  |  |  |  |     pubexp = base64.urlsafe_b64encode(number.long_to_bytes(privkey.e)).decode("utf-8") | 
					
						
							|  |  |  |  |     return f"data:application/magic-public-key,RSA.{mod}.{pubexp}" | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-19 14:03:34 +00:00
										 |  |  |  | def storeWebfingerEndpoint(nickname: str,domain: str,port: int,baseDir: str, \ | 
					
						
							| 
									
										
										
										
											2019-07-02 20:54:22 +00:00
										 |  |  |  |                            wfJson: {}) -> bool: | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     """Stores webfinger endpoint for a user to a file
 | 
					
						
							|  |  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2019-08-23 20:03:06 +00:00
										 |  |  |  |     originalDomain=domain | 
					
						
							| 
									
										
										
										
											2019-08-16 20:35:11 +00:00
										 |  |  |  |     if port: | 
					
						
							|  |  |  |  |         if port!=80 and port!=443: | 
					
						
							|  |  |  |  |             if ':' not in domain: | 
					
						
							|  |  |  |  |                 domain=domain+':'+str(port) | 
					
						
							| 
									
										
										
										
											2019-07-03 09:40:27 +00:00
										 |  |  |  |     handle=nickname+'@'+domain | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     wfSubdir='/wfendpoints' | 
					
						
							|  |  |  |  |     if not os.path.isdir(baseDir+wfSubdir): | 
					
						
							|  |  |  |  |         os.mkdir(baseDir+wfSubdir) | 
					
						
							|  |  |  |  |     filename=baseDir+wfSubdir+'/'+handle.lower()+'.json' | 
					
						
							|  |  |  |  |     with open(filename, 'w') as fp: | 
					
						
							|  |  |  |  |         commentjson.dump(wfJson, fp, indent=4, sort_keys=False) | 
					
						
							| 
									
										
										
										
											2019-08-23 20:03:06 +00:00
										 |  |  |  |     if nickname=='inbox': | 
					
						
							|  |  |  |  |         handle=originalDomain+'@'+domain | 
					
						
							|  |  |  |  |         filename=baseDir+wfSubdir+'/'+handle.lower()+'.json' | 
					
						
							|  |  |  |  |         with open(filename, 'w') as fp: | 
					
						
							|  |  |  |  |             commentjson.dump(wfJson, fp, indent=4, sort_keys=False)         | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     return True | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-03 09:40:27 +00:00
										 |  |  |  | def createWebfingerEndpoint(nickname: str,domain: str,port: int, \ | 
					
						
							| 
									
										
										
										
											2019-07-03 19:00:03 +00:00
										 |  |  |  |                             httpPrefix: str,publicKeyPem) -> {}: | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     """Creates a webfinger endpoint for a user
 | 
					
						
							|  |  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2019-07-19 14:41:32 +00:00
										 |  |  |  |     originalDomain=domain | 
					
						
							| 
									
										
										
										
											2019-08-16 20:35:11 +00:00
										 |  |  |  |     if port: | 
					
						
							|  |  |  |  |         if port!=80 and port!=443: | 
					
						
							|  |  |  |  |             if ':' not in domain: | 
					
						
							|  |  |  |  |                 domain=domain+':'+str(port) | 
					
						
							| 
									
										
										
										
											2019-06-30 18:23:18 +00:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-23 20:03:06 +00:00
										 |  |  |  |     personName=nickname | 
					
						
							|  |  |  |  |     personId=httpPrefix+"://"+domain+"/users/"+personName | 
					
						
							|  |  |  |  |     subjectStr="acct:"+personName+"@"+originalDomain | 
					
						
							| 
									
										
										
										
											2019-08-26 15:20:14 +00:00
										 |  |  |  |     profilePageHref=httpPrefix+"://"+domain+"/@"+nickname | 
					
						
							| 
									
										
										
										
											2019-08-23 20:03:06 +00:00
										 |  |  |  |     if nickname=='inbox' or nickname==originalDomain: | 
					
						
							|  |  |  |  |         personName='actor' | 
					
						
							|  |  |  |  |         personId=httpPrefix+"://"+domain+"/"+personName | 
					
						
							|  |  |  |  |         subjectStr="acct:"+originalDomain+"@"+originalDomain | 
					
						
							| 
									
										
										
										
											2019-08-26 15:20:14 +00:00
										 |  |  |  |         profilePageHref=httpPrefix+'://'+domain+'/about/more?instance_actor=true' | 
					
						
							|  |  |  |  |      | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     account = { | 
					
						
							|  |  |  |  |         "aliases": [ | 
					
						
							| 
									
										
										
										
											2019-08-23 20:03:06 +00:00
										 |  |  |  |             httpPrefix+"://"+domain+"/@"+personName, | 
					
						
							|  |  |  |  |             personId | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |         ], | 
					
						
							|  |  |  |  |         "links": [ | 
					
						
							|  |  |  |  |             { | 
					
						
							| 
									
										
										
										
											2019-08-26 15:20:14 +00:00
										 |  |  |  |                 "href": profilePageHref, | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |                 "rel": "http://webfinger.net/rel/profile-page", | 
					
						
							|  |  |  |  |                 "type": "text/html" | 
					
						
							|  |  |  |  |             }, | 
					
						
							| 
									
										
										
										
											2019-08-26 14:30:09 +00:00
										 |  |  |  |             { | 
					
						
							|  |  |  |  |                 "href": httpPrefix+"://"+domain+"/users/"+nickname+".atom", | 
					
						
							|  |  |  |  |                 "rel": "http://schemas.google.com/g/2010#updates-from", | 
					
						
							|  |  |  |  |                 "type": "application/atom+xml" | 
					
						
							|  |  |  |  |             }, | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |             { | 
					
						
							| 
									
										
										
										
											2019-08-23 20:03:06 +00:00
										 |  |  |  |                 "href": personId, | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |                 "rel": "self", | 
					
						
							|  |  |  |  |                 "type": "application/activity+json" | 
					
						
							|  |  |  |  |             }, | 
					
						
							|  |  |  |  |             { | 
					
						
							|  |  |  |  |                 "href": generateMagicKey(publicKeyPem), | 
					
						
							|  |  |  |  |                 "rel": "magic-public-key" | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  |         ], | 
					
						
							| 
									
										
										
										
											2019-08-23 20:05:16 +00:00
										 |  |  |  |         "subject": subjectStr | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  |     return account | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-16 20:52:55 +00:00
										 |  |  |  | def webfingerMeta(httpPrefix: str,domainFull: str) -> str: | 
					
						
							|  |  |  |  |     """Return /.well-known/host-meta
 | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2019-08-26 14:30:09 +00:00
										 |  |  |  |     #return \ | 
					
						
							|  |  |  |  |     #    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" \ | 
					
						
							|  |  |  |  |     #    "<XRD xmlns=\"http://docs.oasis-open.org/ns/xri/xrd-1.0\">" \ | 
					
						
							|  |  |  |  |     #    "<Link rel=\"lrdd\" type=\"application/xrd+xml\" template=\""+httpPrefix+"://"+domainFull+"/.well-known/webfinger?resource={uri}\"/>" \ | 
					
						
							|  |  |  |  |     #    "</XRD>" | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-16 20:52:55 +00:00
										 |  |  |  |     return \ | 
					
						
							| 
									
										
										
										
											2019-08-26 14:30:09 +00:00
										 |  |  |  |         "<?xml version=’1.0' encoding=’UTF-8'?>" \ | 
					
						
							|  |  |  |  |         "<XRD xmlns=’http://docs.oasis-open.org/ns/xri/xrd-1.0'" \ | 
					
						
							|  |  |  |  |         " xmlns:hm=’http://host-meta.net/xrd/1.0'>" \ | 
					
						
							|  |  |  |  |         "" \ | 
					
						
							|  |  |  |  |         "<hm:Host>"+domainFull+"</hm:Host>" \ | 
					
						
							|  |  |  |  |         "" \ | 
					
						
							|  |  |  |  |         "<Link rel=’lrdd’" \ | 
					
						
							|  |  |  |  |         " template=’"+httpPrefix+"://"+domainFull+"/describe?uri={uri}'>" \ | 
					
						
							|  |  |  |  |         " <Title>Resource Descriptor</Title>" \ | 
					
						
							|  |  |  |  |         " </Link>" \ | 
					
						
							| 
									
										
										
										
											2019-08-16 20:52:55 +00:00
										 |  |  |  |         "</XRD>" | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-19 14:41:32 +00:00
										 |  |  |  | def webfingerLookup(path: str,baseDir: str,port: int,debug: bool) -> {}: | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     """Lookup the webfinger endpoint for an account
 | 
					
						
							|  |  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2019-07-19 14:19:36 +00:00
										 |  |  |  |     if not path.startswith('/.well-known/webfinger?'):         | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |         return None | 
					
						
							|  |  |  |  |     handle=None | 
					
						
							|  |  |  |  |     if 'resource=acct:' in path: | 
					
						
							| 
									
										
										
										
											2019-07-19 14:19:36 +00:00
										 |  |  |  |         handle=path.split('resource=acct:')[1].strip() | 
					
						
							|  |  |  |  |         if debug: | 
					
						
							|  |  |  |  |             print('DEBUG: WEBFINGER handle '+handle) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     else: | 
					
						
							|  |  |  |  |         if 'resource=acct%3A' in path: | 
					
						
							| 
									
										
										
										
											2019-07-19 14:41:32 +00:00
										 |  |  |  |             handle=path.split('resource=acct%3A')[1].replace('%40','@',1).replace('%3A',':',1).strip() | 
					
						
							| 
									
										
										
										
											2019-07-19 14:19:36 +00:00
										 |  |  |  |             if debug: | 
					
						
							|  |  |  |  |                 print('DEBUG: WEBFINGER handle '+handle) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     if not handle: | 
					
						
							| 
									
										
										
										
											2019-07-19 14:19:36 +00:00
										 |  |  |  |         if debug: | 
					
						
							|  |  |  |  |             print('DEBUG: WEBFINGER handle missing') | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |         return None | 
					
						
							|  |  |  |  |     if '&' in handle: | 
					
						
							|  |  |  |  |         handle=handle.split('&')[0].strip() | 
					
						
							| 
									
										
										
										
											2019-07-19 14:19:36 +00:00
										 |  |  |  |         if debug: | 
					
						
							|  |  |  |  |             print('DEBUG: WEBFINGER handle with & removed '+handle) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     if '@' not in handle: | 
					
						
							| 
									
										
										
										
											2019-07-19 14:19:36 +00:00
										 |  |  |  |         if debug: | 
					
						
							|  |  |  |  |             print('DEBUG: WEBFINGER no @ in handle '+handle) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |         return None | 
					
						
							| 
									
										
										
										
											2019-08-16 20:35:11 +00:00
										 |  |  |  |     if port: | 
					
						
							|  |  |  |  |         if port!=80 and port !=443: | 
					
						
							|  |  |  |  |             if ':' not in handle: | 
					
						
							|  |  |  |  |                 handle=handle+':'+str(port) | 
					
						
							| 
									
										
										
										
											2019-08-23 14:18:31 +00:00
										 |  |  |  |     # convert @domain@domain to inbox@domain | 
					
						
							|  |  |  |  |     if '@' in handle: | 
					
						
							|  |  |  |  |         handleDomain=handle.split('@')[1] | 
					
						
							| 
									
										
										
										
											2019-08-23 14:19:33 +00:00
										 |  |  |  |         if handle.startswith(handleDomain+'@'): | 
					
						
							| 
									
										
										
										
											2019-08-23 14:18:31 +00:00
										 |  |  |  |             handle='inbox@'+handleDomain | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     filename=baseDir+'/wfendpoints/'+handle.lower()+'.json' | 
					
						
							| 
									
										
										
										
											2019-07-19 14:19:36 +00:00
										 |  |  |  |     if debug: | 
					
						
							| 
									
										
										
										
											2019-07-19 14:41:32 +00:00
										 |  |  |  |         print('DEBUG: WEBFINGER filename '+filename) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     if not os.path.isfile(filename): | 
					
						
							| 
									
										
										
										
											2019-07-19 14:19:36 +00:00
										 |  |  |  |         if debug: | 
					
						
							|  |  |  |  |             print('DEBUG: WEBFINGER filename not found '+filename) | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |         return None | 
					
						
							| 
									
										
										
										
											2019-07-03 09:42:54 +00:00
										 |  |  |  |     wfJson={"nickname": "unknown"} | 
					
						
							| 
									
										
										
										
											2019-06-28 18:55:29 +00:00
										 |  |  |  |     with open(filename, 'r') as fp: | 
					
						
							|  |  |  |  |         wfJson=commentjson.load(fp) | 
					
						
							|  |  |  |  |     return wfJson |