2012-11-02 20:04:04 +00:00
|
|
|
import logging
|
|
|
|
|
2013-11-01 16:26:47 +00:00
|
|
|
__version__ = '1.0'
|
2012-04-04 22:19:34 +00:00
|
|
|
|
2010-02-13 04:53:49 +00:00
|
|
|
RELS = {
|
2012-04-04 21:58:54 +00:00
|
|
|
'activity_streams': 'http://activitystrea.ms/spec/1.0',
|
2010-02-13 04:53:49 +00:00
|
|
|
'avatar': 'http://webfinger.net/rel/avatar',
|
|
|
|
'hcard': 'http://microformats.org/profile/hcard',
|
|
|
|
'open_id': 'http://specs.openid.net/auth/2.0/provider',
|
2012-04-04 21:58:54 +00:00
|
|
|
'opensocial': 'http://ns.opensocial.org/2008/opensocial/activitystreams',
|
2010-02-13 04:53:49 +00:00
|
|
|
'portable_contacts': 'http://portablecontacts.net/spec/1.0',
|
|
|
|
'profile': 'http://webfinger.net/rel/profile-page',
|
2013-11-01 16:36:16 +00:00
|
|
|
'webfist': 'http://webfist.org/spec/rel',
|
2010-02-13 04:53:49 +00:00
|
|
|
'xfn': 'http://gmpg.org/xfn/11',
|
|
|
|
}
|
2010-02-11 22:52:34 +00:00
|
|
|
|
2013-11-01 16:26:47 +00:00
|
|
|
WEBFINGER_TYPE = 'application/jrd+json'
|
2013-11-01 20:37:27 +00:00
|
|
|
LEGACY_WEBFINGER_TYPES = ['application/json']
|
2010-02-11 22:52:34 +00:00
|
|
|
|
2012-04-04 21:59:23 +00:00
|
|
|
UNOFFICIAL_ENDPOINTS = {
|
2012-04-04 22:01:56 +00:00
|
|
|
'facebook.com': 'facebook-webfinger.appspot.com',
|
2012-04-04 21:59:23 +00:00
|
|
|
'twitter.com': 'twitter-webfinger.appspot.com',
|
|
|
|
}
|
|
|
|
|
2012-11-02 20:04:04 +00:00
|
|
|
logger = logging.getLogger("webfinger")
|
|
|
|
|
2012-07-30 21:47:25 +00:00
|
|
|
|
2012-04-03 23:38:59 +00:00
|
|
|
class WebFingerException(Exception):
|
2010-02-13 04:53:49 +00:00
|
|
|
pass
|
2010-02-11 22:52:34 +00:00
|
|
|
|
2012-07-30 21:47:25 +00:00
|
|
|
|
2010-02-13 04:53:49 +00:00
|
|
|
class WebFingerResponse(object):
|
2013-11-01 16:26:47 +00:00
|
|
|
""" Response that wraps an RD object. It provides attribute-style access
|
|
|
|
to links for specific rels, responding with the href attribute
|
|
|
|
of the matched element.
|
2012-11-02 20:04:04 +00:00
|
|
|
"""
|
2012-04-03 23:34:45 +00:00
|
|
|
|
2013-11-01 16:26:47 +00:00
|
|
|
def __init__(self, jrd):
|
|
|
|
self.jrd = jrd
|
2012-04-03 23:34:45 +00:00
|
|
|
|
2010-02-13 04:53:49 +00:00
|
|
|
def __getattr__(self, name):
|
|
|
|
if name in RELS:
|
2013-11-01 16:26:47 +00:00
|
|
|
return self.rel(RELS[name])
|
|
|
|
return getattr(self.jrd, name)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def subject(self):
|
|
|
|
return self.jrd.get('subject')
|
|
|
|
|
|
|
|
@property
|
|
|
|
def aliases(self):
|
2013-11-02 06:50:22 +00:00
|
|
|
return self.jrd.get('aliases', [])
|
2013-11-01 16:26:47 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def properties(self):
|
2013-11-02 06:50:22 +00:00
|
|
|
return self.jrd.get('properties', {})
|
2013-11-01 16:26:47 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def links(self):
|
2013-11-02 06:50:22 +00:00
|
|
|
return self.jrd.get('links', [])
|
2013-11-01 16:26:47 +00:00
|
|
|
|
|
|
|
def rel(self, relation, attr='href'):
|
2013-11-02 16:03:37 +00:00
|
|
|
for link in self.links:
|
|
|
|
if link.get('rel') == relation:
|
2013-11-02 21:20:30 +00:00
|
|
|
if attr:
|
|
|
|
return link.get(attr)
|
|
|
|
return link
|
2010-02-13 04:53:49 +00:00
|
|
|
|
2012-07-30 21:47:25 +00:00
|
|
|
|
2010-02-13 04:53:49 +00:00
|
|
|
class WebFingerClient(object):
|
2012-04-03 23:34:45 +00:00
|
|
|
|
2012-11-02 21:25:18 +00:00
|
|
|
def __init__(self, timeout=None, official=False):
|
2013-11-01 16:26:47 +00:00
|
|
|
self.official = official
|
|
|
|
self.timeout = timeout
|
2012-04-04 00:42:59 +00:00
|
|
|
|
2013-11-02 21:20:30 +00:00
|
|
|
def _parse_host(self, resource):
|
|
|
|
|
|
|
|
host = resource.split("@")[-1]
|
|
|
|
|
|
|
|
if host in UNOFFICIAL_ENDPOINTS and not self.official:
|
|
|
|
unofficial_host = UNOFFICIAL_ENDPOINTS[host]
|
|
|
|
logging.debug('host %s is not supported, using unofficial endpoint %s' % (host, unofficial_host))
|
|
|
|
host = unofficial_host
|
|
|
|
|
|
|
|
return host
|
|
|
|
|
|
|
|
def finger(self, resource, host=None, rel=None, raw=False):
|
|
|
|
|
2013-11-02 21:25:06 +00:00
|
|
|
import requests
|
|
|
|
|
2013-11-02 21:20:30 +00:00
|
|
|
if not host:
|
|
|
|
host = self._parse_host(resource)
|
2012-04-04 22:22:34 +00:00
|
|
|
|
2013-11-01 16:26:47 +00:00
|
|
|
url = "https://%s/.well-known/webfinger" % host
|
2012-04-04 22:22:34 +00:00
|
|
|
|
2013-11-01 16:26:47 +00:00
|
|
|
headers = {
|
|
|
|
'User-Agent': 'python-webfinger/%s' % __version__,
|
|
|
|
'Accept': WEBFINGER_TYPE,
|
|
|
|
}
|
2012-07-30 21:47:25 +00:00
|
|
|
|
2013-11-01 16:26:47 +00:00
|
|
|
params = {'resource': resource}
|
2012-11-02 20:46:29 +00:00
|
|
|
if rel:
|
|
|
|
params['rel'] = rel
|
|
|
|
|
2013-11-01 16:26:47 +00:00
|
|
|
resp = requests.get(url, params=params, headers=headers, timeout=self.timeout, verify=True)
|
|
|
|
logging.debug('fetching JRD from %s' % resp.url)
|
2012-04-03 23:34:45 +00:00
|
|
|
|
2013-11-01 20:37:27 +00:00
|
|
|
content_type = resp.headers.get('Content-Type', '').split(';', 1)[0].strip()
|
2013-11-01 16:26:47 +00:00
|
|
|
logging.debug('response content type: %s' % content_type)
|
2012-11-02 20:04:04 +00:00
|
|
|
|
2013-11-01 20:37:27 +00:00
|
|
|
if content_type != WEBFINGER_TYPE and content_type not in LEGACY_WEBFINGER_TYPES:
|
|
|
|
raise WebFingerException('Invalid response type from server')
|
2013-11-01 16:26:47 +00:00
|
|
|
|
|
|
|
if raw:
|
2013-11-02 21:20:30 +00:00
|
|
|
return resp.json()
|
2012-11-02 20:04:04 +00:00
|
|
|
|
2013-11-02 21:20:30 +00:00
|
|
|
return WebFingerResponse(resp.json())
|
2012-11-02 20:04:04 +00:00
|
|
|
|
2012-11-02 20:46:29 +00:00
|
|
|
|
2013-11-02 21:20:30 +00:00
|
|
|
def finger(resource, rel=None):
|
|
|
|
""" Convenience method for invoking WebFingerClient.
|
2012-11-02 20:22:41 +00:00
|
|
|
"""
|
2013-11-02 21:20:30 +00:00
|
|
|
return WebFingerClient().finger(resource, rel=rel)
|
2010-02-11 22:52:34 +00:00
|
|
|
|
2012-07-30 21:47:25 +00:00
|
|
|
|
2010-02-11 22:52:34 +00:00
|
|
|
if __name__ == '__main__':
|
2012-04-04 22:19:34 +00:00
|
|
|
|
2012-11-02 20:04:04 +00:00
|
|
|
import argparse
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(description="Simple webfinger client.")
|
|
|
|
parser.add_argument("acct", metavar="URI", help="account URI")
|
|
|
|
parser.add_argument("-d", "--debug", dest="debug", action="store_true", help="print debug logging output to console")
|
|
|
|
parser.add_argument("-r", "--rel", metavar="REL", dest="rel", help="desired relation")
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
if args.debug:
|
|
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
|
|
|
|
|
|
wf = finger(args.acct, rel=args.rel)
|
|
|
|
|
2013-11-02 06:54:33 +00:00
|
|
|
print("--- %s ---" % wf.subject)
|
2013-11-02 06:50:22 +00:00
|
|
|
|
2012-11-02 20:04:04 +00:00
|
|
|
if args.rel:
|
|
|
|
|
2013-11-02 21:20:30 +00:00
|
|
|
link = wf.rel(args.rel)
|
2012-04-04 22:19:34 +00:00
|
|
|
|
2012-11-02 20:04:04 +00:00
|
|
|
if link is None:
|
2013-11-02 06:54:33 +00:00
|
|
|
print("*** Link not found for rel=%s" % args.rel)
|
2012-04-04 22:19:34 +00:00
|
|
|
|
2013-11-02 21:20:30 +00:00
|
|
|
print("%s:\n\t%s" % (args.rel, link))
|
2012-04-04 22:19:34 +00:00
|
|
|
|
2012-11-02 20:04:04 +00:00
|
|
|
else:
|
2012-04-04 22:19:34 +00:00
|
|
|
|
2013-11-02 06:54:33 +00:00
|
|
|
print("Activity Streams: ", wf.activity_streams)
|
|
|
|
print("Avatar: ", wf.avatar)
|
|
|
|
print("HCard: ", wf.hcard)
|
|
|
|
print("OpenID: ", wf.open_id)
|
|
|
|
print("Open Social: ", wf.opensocial)
|
|
|
|
print("Portable Contacts: ", wf.portable_contacts)
|
|
|
|
print("Profile: ", wf.profile)
|
|
|
|
print("WebFist: ", wf.webfist)
|
|
|
|
print("XFN: ", wf.rel("http://gmpg.org/xfn/11"))
|