Skip to content

Commit

Permalink
Merge pull request #2334 from LukeShu/lukeshu/libravatar
Browse files Browse the repository at this point in the history
  • Loading branch information
Changaco authored Apr 9, 2024
2 parents ecab2ec + 202b69d commit a4deb11
Showing 1 changed file with 67 additions and 2 deletions.
69 changes: 67 additions & 2 deletions liberapay/models/participant.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from hashlib import pbkdf2_hmac, md5, sha1
from operator import attrgetter, itemgetter
from os import urandom
from random import randint
from threading import Lock
from time import sleep
from types import SimpleNamespace
Expand All @@ -16,6 +17,8 @@
import aspen_jinja2_renderer
from cached_property import cached_property
from dateutil.parser import parse as parse_date
from dns.exception import DNSException
from dns.resolver import Cache as DNSCache, Resolver as DNSResolver
from html2text import html2text
from markupsafe import escape as htmlescape
from pando import json, Response
Expand Down Expand Up @@ -99,6 +102,10 @@

email_lock = Lock()

DNS = DNSResolver()
DNS.lifetime = 1.0 # 1 second timeout, per https://github.com/liberapay/liberapay.com/pull/1043#issuecomment-377891723
DNS.cache = DNSCache()


class Participant(Model, MixinTeam):

Expand Down Expand Up @@ -2181,8 +2188,66 @@ def update_avatar(self, src=None, cursor=None, avatar_email=None, check=True):
if platform == 'libravatar' or platform is None and email:
if not email:
return
avatar_id = md5(email.strip().lower().encode('utf8')).hexdigest()
avatar_url = 'https://seccdn.libravatar.org/avatar/'+avatar_id
# https://wiki.libravatar.org/api/
#
# We only use the first SRV record that we choose; if there is an
# error talking to that server, we give up, instead of retrying with
# another record. pyLibravatar does the same.
normalized_email = email.strip().lower()
avatar_origin = 'https://seccdn.libravatar.org'
try:
# Look up the SRV record to use.
email_domain = normalized_email.rsplit('@', 1)[1]
try:
srv_records = DNS.query('_avatars-sec._tcp.'+email_domain, 'SRV')
scheme = 'https'
except Exception:
srv_records = DNS.query('_avatars._tcp.'+email_domain, 'SRV')
scheme = 'http'
# Filter down to just the records with the "highest" `.priority`
# (lower number = higher priority); for the libravatar API tells us:
#
# > Libravatar clients MUST only consider servers listed in the
# > highest SRV priority.
top_priority = min(rec.priority for rec in srv_records)
srv_records = [rec for rec in srv_records if rec.priority == top_priority]
# Of those, choose randomly based on their relative `.weight`s;
# for the libravatar API tells us:
#
# > They MUST honour relative weights.
#
# RFC 2782 (at the top of page 4) gives us this algorithm for
# randomly selecting a record based on the weights:
srv_records.sort(key=attrgetter('weight')) # ensure that .weight=0 recs are first in the list
weight_choice = randint(0, sum(rec.weight for rec in srv_records))
weight_sum = 0
for rec in srv_records:
weight_sum += rec.weight
if weight_sum >= weight_choice:
choice_record = rec
break

# Build the `avatar_origin` URL.
# The Dnspython library has already validated that `.target` is
# a valid DNS name and that `.port` is a uint16.
host = choice_record.target.canonicalize().to_text(omit_final_dot=True)
port = choice_record.port
if port == 0:
# Port zero isn't supposed to be used and typically can't be. The
# Libravatar wiki doesn't clearly specify what to do in this case.
pass
elif (scheme == 'http' and port != 80) or (scheme == 'https' and port != 443):
# Only include an explicit port number if it's not the default
# port for the scheme.
avatar_origin = '%s://%s:%d' % (scheme, host, port)
else:
avatar_origin = '%s://%s' % (scheme, host)
except DNSException:
pass
except Exception as e:
website.tell_sentry(e)
avatar_id = md5(normalized_email.encode('utf8')).hexdigest()
avatar_url = avatar_origin + '/avatar/' + avatar_id
avatar_url += AVATAR_QUERY

elif platform is None:
Expand Down

0 comments on commit a4deb11

Please sign in to comment.