2020-04-04 14:14:25 +00:00
|
|
|
|
__filename__ = "webfinger.py"
|
|
|
|
|
__author__ = "Bob Mottram"
|
|
|
|
|
__license__ = "AGPL3+"
|
2022-02-03 13:58:20 +00:00
|
|
|
|
__version__ = "1.3.0"
|
2020-04-04 14:14:25 +00:00
|
|
|
|
__maintainer__ = "Bob Mottram"
|
2021-09-10 16:14:50 +00:00
|
|
|
|
__email__ = "bob@libreserver.org"
|
2020-04-04 14:14:25 +00:00
|
|
|
|
__status__ = "Production"
|
2021-06-15 15:08:12 +00:00
|
|
|
|
__module_group__ = "ActivityPub"
|
2019-06-28 18:55:29 +00:00
|
|
|
|
|
|
|
|
|
import os
|
2020-04-15 11:10:30 +00:00
|
|
|
|
import urllib.parse
|
2021-12-29 21:55:09 +00:00
|
|
|
|
from session import get_json
|
|
|
|
|
from cache import store_webfinger_in_cache
|
|
|
|
|
from cache import get_webfinger_from_cache
|
2022-12-18 15:29:54 +00:00
|
|
|
|
from utils import acct_handle_dir
|
2022-05-11 17:17:23 +00:00
|
|
|
|
from utils import get_attachment_property_value
|
2021-12-26 12:45:03 +00:00
|
|
|
|
from utils import get_full_domain
|
2021-12-26 15:13:34 +00:00
|
|
|
|
from utils import load_json
|
|
|
|
|
from utils import load_json_onionify
|
2021-12-26 14:47:21 +00:00
|
|
|
|
from utils import save_json
|
2021-12-27 17:20:01 +00:00
|
|
|
|
from utils import get_protocol_prefixes
|
2021-12-26 18:17:37 +00:00
|
|
|
|
from utils import remove_domain_port
|
2021-12-26 12:24:40 +00:00
|
|
|
|
from utils import get_user_paths
|
2021-12-26 17:53:07 +00:00
|
|
|
|
from utils import get_group_paths
|
2021-12-26 10:19:59 +00:00
|
|
|
|
from utils import local_actor_url
|
2019-06-28 18:55:29 +00:00
|
|
|
|
|
2020-04-04 14:14:25 +00:00
|
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
|
def _parse_handle(handle: str) -> (str, str, bool):
|
2021-07-29 22:41:27 +00:00
|
|
|
|
"""Parses a handle and returns nickname and domain
|
|
|
|
|
"""
|
2021-12-26 00:07:44 +00:00
|
|
|
|
group_account = False
|
2019-06-28 18:55:29 +00:00
|
|
|
|
if '.' not in handle:
|
2021-07-30 13:00:23 +00:00
|
|
|
|
return None, None, False
|
2021-12-27 17:20:01 +00:00
|
|
|
|
prefixes = get_protocol_prefixes()
|
2022-01-03 21:17:44 +00:00
|
|
|
|
handle_str = handle
|
2020-06-11 12:16:45 +00:00
|
|
|
|
for prefix in prefixes:
|
2022-01-03 21:17:44 +00:00
|
|
|
|
handle_str = handle_str.replace(prefix, '')
|
2021-07-29 22:41:27 +00:00
|
|
|
|
|
|
|
|
|
# try domain/@nick
|
2019-06-28 18:55:29 +00:00
|
|
|
|
if '/@' in handle:
|
2022-01-03 21:17:44 +00:00
|
|
|
|
domain, nickname = handle_str.split('/@')
|
2021-07-30 13:00:23 +00:00
|
|
|
|
return nickname, domain, False
|
2021-07-29 22:41:27 +00:00
|
|
|
|
|
|
|
|
|
# try nick@domain
|
|
|
|
|
if '@' in handle:
|
2021-07-30 13:00:23 +00:00
|
|
|
|
if handle.startswith('!'):
|
|
|
|
|
handle = handle[1:]
|
2021-12-26 00:07:44 +00:00
|
|
|
|
group_account = True
|
2021-07-29 22:41:27 +00:00
|
|
|
|
nickname, domain = handle.split('@')
|
2021-12-26 00:07:44 +00:00
|
|
|
|
return nickname, domain, group_account
|
2021-07-29 22:41:27 +00:00
|
|
|
|
|
|
|
|
|
# try for different /users/ paths
|
2022-01-03 21:17:44 +00:00
|
|
|
|
users_paths = get_user_paths()
|
|
|
|
|
group_paths = get_group_paths()
|
|
|
|
|
for possible_users_path in users_paths:
|
|
|
|
|
if possible_users_path in handle:
|
|
|
|
|
if possible_users_path in group_paths:
|
2021-12-26 00:07:44 +00:00
|
|
|
|
group_account = True
|
2022-01-03 21:17:44 +00:00
|
|
|
|
domain, nickname = handle_str.split(possible_users_path)
|
2021-12-26 00:07:44 +00:00
|
|
|
|
return nickname, domain, group_account
|
2021-07-29 22:41:27 +00:00
|
|
|
|
|
2021-07-30 13:00:23 +00:00
|
|
|
|
return None, None, False
|
2019-06-28 18:55:29 +00:00
|
|
|
|
|
|
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
|
def webfinger_handle(session, handle: str, http_prefix: str,
|
|
|
|
|
cached_webfingers: {},
|
2022-01-03 21:17:44 +00:00
|
|
|
|
from_domain: str, project_version: str,
|
2021-12-29 21:55:09 +00:00
|
|
|
|
debug: bool, group_account: bool,
|
|
|
|
|
signing_priv_key_pem: str) -> {}:
|
2020-06-23 10:41:12 +00:00
|
|
|
|
"""Gets webfinger result for the given ActivityPub handle
|
2022-06-08 22:17:17 +00:00
|
|
|
|
NOTE: in earlier implementations group_account modified the acct prefix.
|
|
|
|
|
This has been left in, because currently there is still no consensus
|
|
|
|
|
about how groups should be implemented.
|
2020-05-07 13:21:58 +00:00
|
|
|
|
"""
|
2019-07-16 10:19:04 +00:00
|
|
|
|
if not session:
|
2022-03-11 15:00:01 +00:00
|
|
|
|
print('WARN: No session specified for webfinger_handle')
|
2019-07-16 10:19:04 +00:00
|
|
|
|
return None
|
2019-07-19 13:32:58 +00:00
|
|
|
|
|
2022-01-03 21:17:44 +00:00
|
|
|
|
nickname, domain, _ = _parse_handle(handle)
|
2019-07-03 09:40:27 +00:00
|
|
|
|
if not nickname:
|
2022-03-11 15:00:01 +00:00
|
|
|
|
print('WARN: No nickname found in handle ' + handle)
|
2019-06-28 18:55:29 +00:00
|
|
|
|
return None
|
2022-01-03 21:17:44 +00:00
|
|
|
|
wf_domain = remove_domain_port(domain)
|
2021-06-23 21:31:50 +00:00
|
|
|
|
|
2022-01-03 21:17:44 +00:00
|
|
|
|
wf_handle = nickname + '@' + wf_domain
|
2022-02-26 10:25:27 +00:00
|
|
|
|
if debug:
|
|
|
|
|
print('Parsed webfinger handle: ' + handle + ' -> ' + wf_handle)
|
2022-01-03 21:17:44 +00:00
|
|
|
|
wfg = get_webfinger_from_cache(wf_handle, cached_webfingers)
|
|
|
|
|
if wfg:
|
2021-03-14 19:22:58 +00:00
|
|
|
|
if debug:
|
2022-01-03 21:17:44 +00:00
|
|
|
|
print('Webfinger from cache: ' + str(wfg))
|
|
|
|
|
return wfg
|
2021-12-25 17:09:22 +00:00
|
|
|
|
url = '{}://{}/.well-known/webfinger'.format(http_prefix, domain)
|
2020-04-04 14:14:25 +00:00
|
|
|
|
hdr = {
|
2020-03-22 20:36:19 +00:00
|
|
|
|
'Accept': 'application/jrd+json'
|
|
|
|
|
}
|
2021-11-18 17:14:59 +00:00
|
|
|
|
par = {
|
2022-01-03 21:17:44 +00:00
|
|
|
|
'resource': 'acct:{}'.format(wf_handle)
|
2021-11-18 17:14:59 +00:00
|
|
|
|
}
|
2019-07-04 17:31:41 +00:00
|
|
|
|
try:
|
2020-04-04 14:14:25 +00:00
|
|
|
|
result = \
|
2021-12-29 21:55:09 +00:00
|
|
|
|
get_json(signing_priv_key_pem, session, url, hdr, par,
|
2022-01-03 21:17:44 +00:00
|
|
|
|
debug, project_version, http_prefix, from_domain)
|
2021-12-25 15:28:52 +00:00
|
|
|
|
except Exception as ex:
|
2022-02-26 10:25:27 +00:00
|
|
|
|
print('ERROR: webfinger_handle ' + wf_handle + ' ' + str(ex))
|
2021-07-30 13:00:23 +00:00
|
|
|
|
return None
|
2020-05-07 13:21:58 +00:00
|
|
|
|
|
2022-02-26 11:01:50 +00:00
|
|
|
|
# if the first attempt fails then try specifying the webfinger
|
|
|
|
|
# resource in a different way
|
|
|
|
|
if not result:
|
|
|
|
|
resource = handle
|
|
|
|
|
if handle == wf_handle:
|
|
|
|
|
# reconstruct the actor
|
|
|
|
|
resource = http_prefix + '://' + wf_domain + '/users/' + nickname
|
|
|
|
|
# try again using the actor as the resource
|
|
|
|
|
# See https://datatracker.ietf.org/doc/html/rfc7033 section 4.5
|
|
|
|
|
par = {
|
|
|
|
|
'resource': '{}'.format(resource)
|
|
|
|
|
}
|
|
|
|
|
try:
|
|
|
|
|
result = \
|
|
|
|
|
get_json(signing_priv_key_pem, session, url, hdr, par,
|
|
|
|
|
debug, project_version, http_prefix, from_domain)
|
|
|
|
|
except Exception as ex:
|
|
|
|
|
print('ERROR: webfinger_handle ' + wf_handle + ' ' + str(ex))
|
|
|
|
|
return None
|
|
|
|
|
|
2020-05-07 13:21:58 +00:00
|
|
|
|
if result:
|
2022-01-03 21:17:44 +00:00
|
|
|
|
store_webfinger_in_cache(wf_handle, result, cached_webfingers)
|
2020-05-07 13:26:55 +00:00
|
|
|
|
else:
|
2022-05-28 17:01:43 +00:00
|
|
|
|
print("WARN: Unable to webfinger " + str(url) + ' ' +
|
|
|
|
|
'from_domain: ' + str(from_domain) + ' ' +
|
2022-03-11 15:00:01 +00:00
|
|
|
|
'nickname: ' + str(nickname) + ' ' +
|
|
|
|
|
'handle: ' + str(handle) + ' ' +
|
|
|
|
|
'wf_handle: ' + str(wf_handle) + ' ' +
|
|
|
|
|
'domain: ' + str(wf_domain) + ' ' +
|
|
|
|
|
'headers: ' + str(hdr) + ' ' +
|
|
|
|
|
'params: ' + str(par))
|
2020-05-07 13:26:55 +00:00
|
|
|
|
|
2019-06-28 18:55:29 +00:00
|
|
|
|
return result
|
|
|
|
|
|
2020-04-04 14:14:25 +00:00
|
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
|
def store_webfinger_endpoint(nickname: str, domain: str, port: int,
|
2022-01-03 21:17:44 +00:00
|
|
|
|
base_dir: str, wf_json: {}) -> bool:
|
2019-06-28 18:55:29 +00:00
|
|
|
|
"""Stores webfinger endpoint for a user to a file
|
|
|
|
|
"""
|
2022-01-03 21:17:44 +00:00
|
|
|
|
original_domain = domain
|
2021-12-26 12:45:03 +00:00
|
|
|
|
domain = get_full_domain(domain, port)
|
2020-04-04 14:14:25 +00:00
|
|
|
|
handle = nickname + '@' + domain
|
2022-01-03 21:17:44 +00:00
|
|
|
|
wf_subdir = '/wfendpoints'
|
|
|
|
|
if not os.path.isdir(base_dir + wf_subdir):
|
|
|
|
|
os.mkdir(base_dir + wf_subdir)
|
|
|
|
|
filename = base_dir + wf_subdir + '/' + handle + '.json'
|
|
|
|
|
save_json(wf_json, filename)
|
2020-04-04 14:14:25 +00:00
|
|
|
|
if nickname == 'inbox':
|
2022-01-03 21:17:44 +00:00
|
|
|
|
handle = original_domain + '@' + domain
|
|
|
|
|
filename = base_dir + wf_subdir + '/' + handle + '.json'
|
|
|
|
|
save_json(wf_json, filename)
|
2019-06-28 18:55:29 +00:00
|
|
|
|
return True
|
|
|
|
|
|
2020-04-04 14:14:25 +00:00
|
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
|
def create_webfinger_endpoint(nickname: str, domain: str, port: int,
|
2022-01-03 21:17:44 +00:00
|
|
|
|
http_prefix: str, public_key_pem: str,
|
2021-12-29 21:55:09 +00:00
|
|
|
|
group_account: bool) -> {}:
|
2019-06-28 18:55:29 +00:00
|
|
|
|
"""Creates a webfinger endpoint for a user
|
2022-06-08 22:17:17 +00:00
|
|
|
|
NOTE: in earlier implementations group_account modified the acct prefix.
|
|
|
|
|
This has been left in, because currently there is still no consensus
|
|
|
|
|
about how groups should be implemented.
|
2019-06-28 18:55:29 +00:00
|
|
|
|
"""
|
2022-01-03 21:17:44 +00:00
|
|
|
|
original_domain = domain
|
2021-12-26 12:45:03 +00:00
|
|
|
|
domain = get_full_domain(domain, port)
|
2020-04-04 14:14:25 +00:00
|
|
|
|
|
2022-01-03 21:17:44 +00:00
|
|
|
|
person_name = nickname
|
|
|
|
|
person_id = local_actor_url(http_prefix, person_name, domain)
|
|
|
|
|
subject_str = "acct:" + person_name + "@" + original_domain
|
|
|
|
|
profile_page_href = http_prefix + "://" + domain + "/@" + nickname
|
|
|
|
|
if nickname in ('inbox', original_domain):
|
|
|
|
|
person_name = 'actor'
|
|
|
|
|
person_id = http_prefix + "://" + domain + "/" + person_name
|
|
|
|
|
subject_str = "acct:" + original_domain + "@" + original_domain
|
|
|
|
|
profile_page_href = http_prefix + '://' + domain + \
|
2020-04-04 14:14:25 +00:00
|
|
|
|
'/about/more?instance_actor=true'
|
|
|
|
|
|
2022-01-03 21:17:44 +00:00
|
|
|
|
person_link = http_prefix + "://" + domain + "/@" + person_name
|
|
|
|
|
blog_url = http_prefix + "://" + domain + "/blog/" + person_name
|
2020-04-04 14:14:25 +00:00
|
|
|
|
account = {
|
2019-06-28 18:55:29 +00:00
|
|
|
|
"aliases": [
|
2022-01-03 21:17:44 +00:00
|
|
|
|
person_link,
|
|
|
|
|
person_id
|
2019-06-28 18:55:29 +00:00
|
|
|
|
],
|
|
|
|
|
"links": [
|
2021-10-28 09:33:27 +00:00
|
|
|
|
{
|
2022-01-03 21:17:44 +00:00
|
|
|
|
"href": person_link + "/avatar.png",
|
2021-10-28 09:33:27 +00:00
|
|
|
|
"rel": "http://webfinger.net/rel/avatar",
|
|
|
|
|
"type": "image/png"
|
|
|
|
|
},
|
2021-10-28 09:41:18 +00:00
|
|
|
|
{
|
2022-01-03 21:17:44 +00:00
|
|
|
|
"href": blog_url,
|
2021-10-28 09:41:18 +00:00
|
|
|
|
"rel": "http://webfinger.net/rel/blog"
|
|
|
|
|
},
|
2019-06-28 18:55:29 +00:00
|
|
|
|
{
|
2022-01-03 21:17:44 +00:00
|
|
|
|
"href": profile_page_href,
|
2019-06-28 18:55:29 +00:00
|
|
|
|
"rel": "http://webfinger.net/rel/profile-page",
|
|
|
|
|
"type": "text/html"
|
|
|
|
|
},
|
2022-02-16 10:24:04 +00:00
|
|
|
|
{
|
|
|
|
|
"href": profile_page_href,
|
|
|
|
|
"rel": "http://webfinger.net/rel/profile-page",
|
|
|
|
|
"type": "text/vcard"
|
|
|
|
|
},
|
2019-06-28 18:55:29 +00:00
|
|
|
|
{
|
2022-01-03 21:17:44 +00:00
|
|
|
|
"href": person_id,
|
2019-06-28 18:55:29 +00:00
|
|
|
|
"rel": "self",
|
|
|
|
|
"type": "application/activity+json"
|
|
|
|
|
}
|
|
|
|
|
],
|
2022-01-03 21:17:44 +00:00
|
|
|
|
"subject": subject_str
|
2019-06-28 18:55:29 +00:00
|
|
|
|
}
|
|
|
|
|
return account
|
|
|
|
|
|
2020-04-04 14:14:25 +00:00
|
|
|
|
|
2021-12-28 17:09:43 +00:00
|
|
|
|
def webfinger_node_info(http_prefix: str, domain_full: str) -> {}:
|
2019-11-13 10:32:12 +00:00
|
|
|
|
""" /.well-known/nodeinfo endpoint
|
|
|
|
|
"""
|
2020-04-04 14:14:25 +00:00
|
|
|
|
nodeinfo = {
|
2019-11-13 10:32:12 +00:00
|
|
|
|
'links': [
|
|
|
|
|
{
|
2021-12-26 10:00:46 +00:00
|
|
|
|
'href': http_prefix + '://' + domain_full + '/nodeinfo/2.0',
|
2019-11-13 10:32:12 +00:00
|
|
|
|
'rel': 'http://nodeinfo.diaspora.software/ns/schema/2.0'
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
return nodeinfo
|
|
|
|
|
|
2020-04-04 14:14:25 +00:00
|
|
|
|
|
2021-12-28 17:06:24 +00:00
|
|
|
|
def webfinger_meta(http_prefix: str, domain_full: str) -> str:
|
2019-08-16 20:52:55 +00:00
|
|
|
|
"""Return /.well-known/host-meta
|
2019-06-28 18:55:29 +00:00
|
|
|
|
"""
|
2022-01-03 21:17:44 +00:00
|
|
|
|
meta_str = \
|
2021-07-06 12:50:38 +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'>" + \
|
|
|
|
|
"" + \
|
2021-12-26 10:00:46 +00:00
|
|
|
|
"<hm:Host>" + domain_full + "</hm:Host>" + \
|
2021-07-06 12:50:38 +00:00
|
|
|
|
"" + \
|
|
|
|
|
"<Link rel=’lrdd’" + \
|
2021-12-26 10:00:46 +00:00
|
|
|
|
" template=’" + http_prefix + "://" + domain_full + \
|
2021-07-06 12:50:38 +00:00
|
|
|
|
"/describe?uri={uri}'>" + \
|
|
|
|
|
" <Title>Resource Descriptor</Title>" + \
|
|
|
|
|
" </Link>" + \
|
|
|
|
|
"</XRD>"
|
2022-01-03 21:17:44 +00:00
|
|
|
|
return meta_str
|
2019-08-16 20:52:55 +00:00
|
|
|
|
|
2020-04-04 14:14:25 +00:00
|
|
|
|
|
2023-01-10 14:24:03 +00:00
|
|
|
|
def wellknown_protocol_handler(path: str, http_prefix: str,
|
|
|
|
|
domain_full: str) -> ({}, str):
|
2023-01-09 22:05:58 +00:00
|
|
|
|
"""See https://fedi-to.github.io/protocol-handler.html
|
|
|
|
|
"""
|
|
|
|
|
if not path.startswith('/.well-known/protocol-handler?'):
|
2023-01-10 14:24:03 +00:00
|
|
|
|
return None, None
|
2023-01-09 22:05:58 +00:00
|
|
|
|
|
|
|
|
|
if 'target=' in path:
|
2023-01-09 22:25:32 +00:00
|
|
|
|
path = urllib.parse.unquote(path)
|
2023-01-09 22:05:58 +00:00
|
|
|
|
target = path.split('target=')[1]
|
|
|
|
|
if ';' in target:
|
|
|
|
|
target = target.split(';')[0]
|
|
|
|
|
if not target:
|
2023-01-10 14:24:03 +00:00
|
|
|
|
return None, None
|
2023-01-09 23:29:14 +00:00
|
|
|
|
if not target.startswith('web+epicyon:') and \
|
2023-01-09 23:29:40 +00:00
|
|
|
|
not target.startswith('web+mastodon:') and \
|
2023-01-09 23:29:14 +00:00
|
|
|
|
not target.startswith('web+ap:'):
|
2023-01-10 14:24:03 +00:00
|
|
|
|
return None, None
|
2023-01-09 22:05:58 +00:00
|
|
|
|
handle = target.split(':', 1)[1].strip()
|
2023-01-09 22:18:31 +00:00
|
|
|
|
if handle.startswith('//'):
|
|
|
|
|
handle = handle[2:]
|
2023-01-09 22:05:58 +00:00
|
|
|
|
if handle.startswith('@'):
|
|
|
|
|
handle = handle[1:]
|
|
|
|
|
if '@' in handle:
|
|
|
|
|
nickname = handle.split('@')[0]
|
2023-01-10 14:09:35 +00:00
|
|
|
|
domain_and_path = handle.split('@')[1]
|
2023-01-09 22:05:58 +00:00
|
|
|
|
else:
|
|
|
|
|
nickname = handle
|
2023-01-10 14:09:35 +00:00
|
|
|
|
domain_and_path = domain_full
|
2023-01-09 22:05:58 +00:00
|
|
|
|
# not an open redirect
|
2023-01-10 14:09:35 +00:00
|
|
|
|
if domain_and_path.startswith(domain_full):
|
2023-01-10 14:24:03 +00:00
|
|
|
|
command = ''
|
|
|
|
|
if '/' in nickname:
|
|
|
|
|
command = nickname.split('/')[0]
|
|
|
|
|
nickname = nickname.split('/')[1]
|
2023-01-10 14:09:35 +00:00
|
|
|
|
domain_length = len(domain_full)
|
|
|
|
|
path_str = domain_and_path[domain_length:]
|
|
|
|
|
return http_prefix + '://' + domain_full + \
|
2023-01-10 14:24:03 +00:00
|
|
|
|
'/users/' + nickname + path_str, command
|
|
|
|
|
return None, None
|
2023-01-09 22:05:58 +00:00
|
|
|
|
|
|
|
|
|
|
2021-12-28 17:20:43 +00:00
|
|
|
|
def webfinger_lookup(path: str, base_dir: str,
|
2022-03-13 20:58:05 +00:00
|
|
|
|
domain: str, onion_domain: str, i2p_domain: str,
|
2021-12-28 17:20:43 +00:00
|
|
|
|
port: int, debug: bool) -> {}:
|
2019-06-28 18:55:29 +00:00
|
|
|
|
"""Lookup the webfinger endpoint for an account
|
|
|
|
|
"""
|
2020-03-22 21:16:02 +00:00
|
|
|
|
if not path.startswith('/.well-known/webfinger?'):
|
2019-06-28 18:55:29 +00:00
|
|
|
|
return None
|
2020-04-04 14:14:25 +00:00
|
|
|
|
handle = None
|
2022-01-03 21:17:44 +00:00
|
|
|
|
res_type = 'acct'
|
|
|
|
|
if 'resource=' + res_type + ':' in path:
|
|
|
|
|
handle = path.split('resource=' + res_type + ':')[1].strip()
|
2021-11-18 17:14:59 +00:00
|
|
|
|
handle = urllib.parse.unquote(handle)
|
|
|
|
|
if debug:
|
|
|
|
|
print('DEBUG: WEBFINGER handle ' + handle)
|
2022-01-03 21:17:44 +00:00
|
|
|
|
elif 'resource=' + res_type + '%3A' in path:
|
|
|
|
|
handle = path.split('resource=' + res_type + '%3A')[1]
|
2021-11-18 17:14:59 +00:00
|
|
|
|
handle = urllib.parse.unquote(handle.strip())
|
|
|
|
|
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:
|
2020-04-04 14:14:25 +00:00
|
|
|
|
handle = handle.split('&')[0].strip()
|
2021-10-28 13:27:25 +00:00
|
|
|
|
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:
|
2020-04-04 14:14:25 +00:00
|
|
|
|
print('DEBUG: WEBFINGER no @ in handle ' + handle)
|
2019-06-28 18:55:29 +00:00
|
|
|
|
return None
|
2021-12-26 12:45:03 +00:00
|
|
|
|
handle = get_full_domain(handle, port)
|
2019-08-23 14:18:31 +00:00
|
|
|
|
# convert @domain@domain to inbox@domain
|
|
|
|
|
if '@' in handle:
|
2022-01-03 21:17:44 +00:00
|
|
|
|
handle_domain = handle.split('@')[1]
|
|
|
|
|
if handle.startswith(handle_domain + '@'):
|
|
|
|
|
handle = 'inbox@' + handle_domain
|
2020-03-02 14:35:44 +00:00
|
|
|
|
# if this is a lookup for a handle using its onion domain
|
|
|
|
|
# then swap the onion domain for the clearnet version
|
2020-04-04 14:14:25 +00:00
|
|
|
|
onionify = False
|
2021-12-25 20:43:43 +00:00
|
|
|
|
if onion_domain:
|
|
|
|
|
if onion_domain in handle:
|
|
|
|
|
handle = handle.replace(onion_domain, domain)
|
2020-04-04 14:14:25 +00:00
|
|
|
|
onionify = True
|
2022-03-13 20:58:05 +00:00
|
|
|
|
i2pify = False
|
|
|
|
|
if i2p_domain:
|
|
|
|
|
if i2p_domain in handle:
|
|
|
|
|
handle = handle.replace(i2p_domain, domain)
|
|
|
|
|
i2pify = True
|
2021-08-31 19:04:29 +00:00
|
|
|
|
# instance actor
|
|
|
|
|
if handle.startswith('actor@'):
|
|
|
|
|
handle = handle.replace('actor@', 'inbox@', 1)
|
|
|
|
|
elif handle.startswith('Actor@'):
|
|
|
|
|
handle = handle.replace('Actor@', 'inbox@', 1)
|
2021-12-25 16:17:53 +00:00
|
|
|
|
filename = base_dir + '/wfendpoints/' + handle + '.json'
|
2019-07-19 14:19:36 +00:00
|
|
|
|
if debug:
|
2020-04-04 14:14:25 +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:
|
2020-04-04 14:14:25 +00:00
|
|
|
|
print('DEBUG: WEBFINGER filename not found ' + filename)
|
2019-06-28 18:55:29 +00:00
|
|
|
|
return None
|
2022-03-13 20:58:05 +00:00
|
|
|
|
if not onionify and not i2pify:
|
2022-01-03 21:17:44 +00:00
|
|
|
|
wf_json = load_json(filename)
|
2022-03-13 20:58:05 +00:00
|
|
|
|
elif onionify:
|
2020-04-04 14:14:25 +00:00
|
|
|
|
print('Webfinger request for onionified ' + handle)
|
2022-01-03 21:17:44 +00:00
|
|
|
|
wf_json = load_json_onionify(filename, domain, onion_domain)
|
2022-03-13 20:58:05 +00:00
|
|
|
|
else:
|
|
|
|
|
print('Webfinger request for i2pified ' + handle)
|
|
|
|
|
wf_json = load_json_onionify(filename, domain, i2p_domain)
|
2022-01-03 21:17:44 +00:00
|
|
|
|
if not wf_json:
|
|
|
|
|
wf_json = {"nickname": "unknown"}
|
|
|
|
|
return wf_json
|
2020-05-04 13:58:24 +00:00
|
|
|
|
|
|
|
|
|
|
2022-02-16 10:24:04 +00:00
|
|
|
|
def _webfinger_update_avatar(wf_json: {}, actor_json: {}) -> bool:
|
2021-10-28 10:08:41 +00:00
|
|
|
|
"""Updates the avatar image link
|
|
|
|
|
"""
|
|
|
|
|
found = False
|
2022-01-03 21:17:44 +00:00
|
|
|
|
avatar_url = actor_json['icon']['url']
|
|
|
|
|
media_type = actor_json['icon']['mediaType']
|
|
|
|
|
for link in wf_json['links']:
|
2021-10-28 10:08:41 +00:00
|
|
|
|
if not link.get('rel'):
|
|
|
|
|
continue
|
|
|
|
|
if not link['rel'].endswith('://webfinger.net/rel/avatar'):
|
|
|
|
|
continue
|
|
|
|
|
found = True
|
2022-01-03 21:17:44 +00:00
|
|
|
|
if link['href'] != avatar_url or link['type'] != media_type:
|
|
|
|
|
link['href'] = avatar_url
|
|
|
|
|
link['type'] = media_type
|
2021-10-28 10:08:41 +00:00
|
|
|
|
return True
|
|
|
|
|
break
|
|
|
|
|
if found:
|
|
|
|
|
return False
|
2022-01-03 21:17:44 +00:00
|
|
|
|
wf_json['links'].append({
|
|
|
|
|
"href": avatar_url,
|
2021-10-28 10:08:41 +00:00
|
|
|
|
"rel": "http://webfinger.net/rel/avatar",
|
2022-01-03 21:17:44 +00:00
|
|
|
|
"type": media_type
|
2021-10-28 10:08:41 +00:00
|
|
|
|
})
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
2022-02-16 10:24:04 +00:00
|
|
|
|
def _webfinger_update_vcard(wf_json: {}, actor_json: {}) -> bool:
|
|
|
|
|
"""Updates the vcard link
|
|
|
|
|
"""
|
|
|
|
|
for link in wf_json['links']:
|
2022-02-24 11:17:32 +00:00
|
|
|
|
if link.get('type'):
|
|
|
|
|
if link['type'] == 'text/vcard':
|
|
|
|
|
return False
|
2022-02-16 10:24:04 +00:00
|
|
|
|
wf_json['links'].append({
|
|
|
|
|
"href": actor_json['url'],
|
|
|
|
|
"rel": "http://webfinger.net/rel/profile-page",
|
|
|
|
|
"type": "text/vcard"
|
|
|
|
|
})
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
2022-01-03 21:17:44 +00:00
|
|
|
|
def _webfinger_add_blog_link(wf_json: {}, actor_json: {}) -> bool:
|
2021-10-28 10:21:28 +00:00
|
|
|
|
"""Adds a blog link to webfinger if needed
|
|
|
|
|
"""
|
|
|
|
|
found = False
|
2021-12-26 10:29:52 +00:00
|
|
|
|
if '/users/' in actor_json['id']:
|
2022-01-03 21:17:44 +00:00
|
|
|
|
blog_url = \
|
2021-12-26 10:29:52 +00:00
|
|
|
|
actor_json['id'].split('/users/')[0] + '/blog/' + \
|
|
|
|
|
actor_json['id'].split('/users/')[1]
|
2021-10-28 10:21:28 +00:00
|
|
|
|
else:
|
2022-01-03 21:17:44 +00:00
|
|
|
|
blog_url = \
|
2021-12-26 10:29:52 +00:00
|
|
|
|
actor_json['id'].split('/@')[0] + '/blog/' + \
|
|
|
|
|
actor_json['id'].split('/@')[1]
|
2022-01-03 21:17:44 +00:00
|
|
|
|
for link in wf_json['links']:
|
2021-10-28 10:21:28 +00:00
|
|
|
|
if not link.get('rel'):
|
|
|
|
|
continue
|
|
|
|
|
if not link['rel'].endswith('://webfinger.net/rel/blog'):
|
|
|
|
|
continue
|
|
|
|
|
found = True
|
2022-01-03 21:17:44 +00:00
|
|
|
|
if link['href'] != blog_url:
|
|
|
|
|
link['href'] = blog_url
|
2021-10-28 10:21:28 +00:00
|
|
|
|
return True
|
|
|
|
|
break
|
|
|
|
|
if found:
|
|
|
|
|
return False
|
2022-01-03 21:17:44 +00:00
|
|
|
|
wf_json['links'].append({
|
|
|
|
|
"href": blog_url,
|
2021-10-28 10:21:28 +00:00
|
|
|
|
"rel": "http://webfinger.net/rel/blog"
|
|
|
|
|
})
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
2022-01-03 21:17:44 +00:00
|
|
|
|
def _webfinger_updateFromProfile(wf_json: {}, actor_json: {}) -> bool:
|
2020-05-04 13:58:24 +00:00
|
|
|
|
"""Updates webfinger Email/blog/xmpp links from profile
|
|
|
|
|
Returns true if one or more tags has been changed
|
|
|
|
|
"""
|
2021-12-26 10:29:52 +00:00
|
|
|
|
if not actor_json.get('attachment'):
|
2020-05-04 13:58:24 +00:00
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
changed = False
|
|
|
|
|
|
2022-01-03 21:17:44 +00:00
|
|
|
|
webfinger_property_name = {
|
2020-05-04 13:58:24 +00:00
|
|
|
|
"xmpp": "xmpp",
|
2020-05-04 14:10:27 +00:00
|
|
|
|
"matrix": "matrix",
|
2020-05-04 13:58:24 +00:00
|
|
|
|
"email": "mailto",
|
|
|
|
|
"ssb": "ssb",
|
2021-07-06 12:53:10 +00:00
|
|
|
|
"briar": "briar",
|
|
|
|
|
"cwtch": "cwtch",
|
2020-05-04 13:58:24 +00:00
|
|
|
|
"tox": "toxId"
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-03 21:17:44 +00:00
|
|
|
|
aliases_not_found = []
|
|
|
|
|
for name, alias in webfinger_property_name.items():
|
|
|
|
|
aliases_not_found.append(alias)
|
2021-07-06 13:11:00 +00:00
|
|
|
|
|
2021-12-26 10:32:45 +00:00
|
|
|
|
for property_value in actor_json['attachment']:
|
2022-05-11 16:10:38 +00:00
|
|
|
|
name_value = None
|
|
|
|
|
if property_value.get('name'):
|
|
|
|
|
name_value = property_value['name']
|
|
|
|
|
elif property_value.get('schema:name'):
|
|
|
|
|
name_value = property_value['schema:name']
|
|
|
|
|
if not name_value:
|
2020-05-04 13:58:24 +00:00
|
|
|
|
continue
|
2022-05-11 16:10:38 +00:00
|
|
|
|
property_name = name_value.lower()
|
2021-07-06 12:50:38 +00:00
|
|
|
|
found = False
|
2022-01-03 21:17:44 +00:00
|
|
|
|
for name, alias in webfinger_property_name.items():
|
2021-12-26 18:19:58 +00:00
|
|
|
|
if name == property_name:
|
2022-01-03 21:17:44 +00:00
|
|
|
|
if alias in aliases_not_found:
|
|
|
|
|
aliases_not_found.remove(alias)
|
2021-07-06 12:50:38 +00:00
|
|
|
|
found = True
|
|
|
|
|
break
|
|
|
|
|
if not found:
|
2020-05-04 13:58:24 +00:00
|
|
|
|
continue
|
2021-12-26 10:32:45 +00:00
|
|
|
|
if not property_value.get('type'):
|
2020-05-04 13:58:24 +00:00
|
|
|
|
continue
|
2022-05-11 17:17:23 +00:00
|
|
|
|
prop_value_name, _ = \
|
|
|
|
|
get_attachment_property_value(property_value)
|
|
|
|
|
if not prop_value_name:
|
2020-05-04 13:58:24 +00:00
|
|
|
|
continue
|
2022-05-11 16:16:34 +00:00
|
|
|
|
if not property_value['type'].endswith('PropertyValue'):
|
2020-05-04 13:58:24 +00:00
|
|
|
|
continue
|
|
|
|
|
|
2022-05-11 17:17:23 +00:00
|
|
|
|
new_value = property_value[prop_value_name].strip()
|
2022-01-03 21:17:44 +00:00
|
|
|
|
if '://' in new_value:
|
|
|
|
|
new_value = new_value.split('://')[1]
|
2021-07-06 13:02:00 +00:00
|
|
|
|
|
2022-01-03 21:17:44 +00:00
|
|
|
|
alias_index = 0
|
2020-05-04 13:58:24 +00:00
|
|
|
|
found = False
|
2022-01-03 21:17:44 +00:00
|
|
|
|
for alias in wf_json['aliases']:
|
|
|
|
|
if alias.startswith(webfinger_property_name[property_name] + ':'):
|
2020-05-04 13:58:24 +00:00
|
|
|
|
found = True
|
|
|
|
|
break
|
2022-01-03 21:17:44 +00:00
|
|
|
|
alias_index += 1
|
|
|
|
|
new_alias = webfinger_property_name[property_name] + ':' + new_value
|
2020-05-04 13:58:24 +00:00
|
|
|
|
if found:
|
2022-01-03 21:17:44 +00:00
|
|
|
|
if wf_json['aliases'][alias_index] != new_alias:
|
2020-05-04 13:58:24 +00:00
|
|
|
|
changed = True
|
2022-01-03 21:17:44 +00:00
|
|
|
|
wf_json['aliases'][alias_index] = new_alias
|
2020-05-04 13:58:24 +00:00
|
|
|
|
else:
|
2022-01-03 21:17:44 +00:00
|
|
|
|
wf_json['aliases'].append(new_alias)
|
2020-05-04 13:58:24 +00:00
|
|
|
|
changed = True
|
2021-07-06 13:11:00 +00:00
|
|
|
|
|
|
|
|
|
# remove any aliases which are no longer in the actor profile
|
2022-01-03 21:17:44 +00:00
|
|
|
|
remove_alias = []
|
|
|
|
|
for alias in aliases_not_found:
|
|
|
|
|
for full_alias in wf_json['aliases']:
|
|
|
|
|
if full_alias.startswith(alias + ':'):
|
|
|
|
|
remove_alias.append(full_alias)
|
|
|
|
|
for full_alias in remove_alias:
|
|
|
|
|
wf_json['aliases'].remove(full_alias)
|
2021-07-06 13:11:00 +00:00
|
|
|
|
changed = True
|
|
|
|
|
|
2022-02-16 10:24:04 +00:00
|
|
|
|
if _webfinger_update_avatar(wf_json, actor_json):
|
|
|
|
|
changed = True
|
|
|
|
|
|
|
|
|
|
if _webfinger_update_vcard(wf_json, actor_json):
|
2021-10-28 10:21:28 +00:00
|
|
|
|
changed = True
|
|
|
|
|
|
2022-01-03 21:17:44 +00:00
|
|
|
|
if _webfinger_add_blog_link(wf_json, actor_json):
|
2021-10-28 10:08:41 +00:00
|
|
|
|
changed = True
|
|
|
|
|
|
2020-05-04 13:58:24 +00:00
|
|
|
|
return changed
|
|
|
|
|
|
|
|
|
|
|
2021-12-28 17:20:43 +00:00
|
|
|
|
def webfinger_update(base_dir: str, nickname: str, domain: str,
|
2022-06-15 14:46:41 +00:00
|
|
|
|
onion_domain: str, i2p_domain: str,
|
2021-12-28 17:20:43 +00:00
|
|
|
|
cached_webfingers: {}) -> None:
|
2022-01-03 21:17:44 +00:00
|
|
|
|
"""Regenerates stored webfinger
|
|
|
|
|
"""
|
2020-05-04 13:58:24 +00:00
|
|
|
|
handle = nickname + '@' + domain
|
2022-01-03 21:17:44 +00:00
|
|
|
|
wf_subdir = '/wfendpoints'
|
|
|
|
|
if not os.path.isdir(base_dir + wf_subdir):
|
2020-05-04 13:58:24 +00:00
|
|
|
|
return
|
|
|
|
|
|
2022-01-03 21:17:44 +00:00
|
|
|
|
filename = base_dir + wf_subdir + '/' + handle + '.json'
|
2020-05-04 13:58:24 +00:00
|
|
|
|
onionify = False
|
2022-06-21 08:31:11 +00:00
|
|
|
|
i2pify = False
|
2021-12-25 20:43:43 +00:00
|
|
|
|
if onion_domain:
|
|
|
|
|
if onion_domain in handle:
|
|
|
|
|
handle = handle.replace(onion_domain, domain)
|
2020-05-04 13:58:24 +00:00
|
|
|
|
onionify = True
|
2022-06-15 14:46:41 +00:00
|
|
|
|
elif i2p_domain:
|
|
|
|
|
if i2p_domain in handle:
|
|
|
|
|
handle = handle.replace(i2p_domain, domain)
|
|
|
|
|
i2pify = True
|
2020-05-04 13:58:24 +00:00
|
|
|
|
if not onionify:
|
2022-06-15 14:46:41 +00:00
|
|
|
|
if not i2pify:
|
|
|
|
|
wf_json = load_json(filename)
|
|
|
|
|
else:
|
|
|
|
|
wf_json = load_json_onionify(filename, domain, i2p_domain)
|
2020-05-04 13:58:24 +00:00
|
|
|
|
else:
|
2022-01-03 21:17:44 +00:00
|
|
|
|
wf_json = load_json_onionify(filename, domain, onion_domain)
|
|
|
|
|
if not wf_json:
|
2020-05-04 13:58:24 +00:00
|
|
|
|
return
|
|
|
|
|
|
2022-12-18 15:29:54 +00:00
|
|
|
|
actor_filename = acct_handle_dir(base_dir, handle) + '.json'
|
2022-01-03 21:17:44 +00:00
|
|
|
|
actor_json = load_json(actor_filename)
|
2021-12-26 10:29:52 +00:00
|
|
|
|
if not actor_json:
|
2020-05-04 13:58:24 +00:00
|
|
|
|
return
|
|
|
|
|
|
2022-01-03 21:17:44 +00:00
|
|
|
|
if _webfinger_updateFromProfile(wf_json, actor_json):
|
|
|
|
|
if save_json(wf_json, filename):
|
|
|
|
|
store_webfinger_in_cache(handle, wf_json, cached_webfingers)
|