Add support for serving users' avatars through the Libravatar API

This commit is contained in:
Danielle McLean 2018-03-23 12:56:13 +11:00
parent 6fb289727c
commit 43348a89da
Signed by: 00dani
GPG Key ID: 5A5D2D1AFF12EEC5
8 changed files with 152 additions and 37 deletions

View File

@ -47,6 +47,7 @@ django-cors-headers = "*"
pytest-django = "*"
"argon2-cffi" = "*"
python-baseconv = "*"
django-computed-property = "*"
[dev-packages]

88
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "a824f0af4df1d0088d5dd66db92329358a5cf0095c71f1cb59f6e60583b912f5"
"sha256": "0b68c7a2821e8063bc9d7bae759a9d8dd57785e341e6d23d6421ad93310c7d1b"
},
"host-environment-markers": {
"implementation_name": "cpython",
@ -11,7 +11,7 @@
"platform_python_implementation": "CPython",
"platform_release": "17.5.0",
"platform_system": "Darwin",
"platform_version": "Darwin Kernel Version 17.5.0: Mon Mar 5 22:58:50 PST 2018; root:xnu-4570.51.1~2/RELEASE_X86_64",
"platform_version": "Darwin Kernel Version 17.5.0: Tue Mar 13 20:39:15 PDT 2018; root:xnu-4570.51.1~36/RELEASE_X86_64",
"python_full_version": "3.6.4",
"python_version": "3.6",
"sys_platform": "darwin"
@ -206,6 +206,12 @@
],
"version": "==2.2"
},
"django-computed-property": {
"hashes": [
"sha256:31aa6453a5c504ce196ba9ae3bacbe0557cadf7ae89e25431b90bf206febd3b3"
],
"version": "==0.2.1"
},
"django-cors-headers": {
"hashes": [
"sha256:0e9532628b3aa8806442d4d0b15e56112e6cfbef3735e13401935c98b842a2b4",
@ -323,36 +329,36 @@
},
"lxml": {
"hashes": [
"sha256:0af9c9267b1257319d49e9c1e9abbf92a99f965bee3c4733e0f0f7578985182d",
"sha256:62bfcd0629991e1c1257ffd28df2ab31a5c44da4c06823c26ec0f472723a84ca",
"sha256:1d1e45584353e4d563685874707fc8c85cdd11b0ef3b79d77bb38046134d68a9",
"sha256:29697224b2df76edf7c2de9bcd90a26dd28fe85c5fd7f0171cae84f8383b227e",
"sha256:988d55112f196e12341b7c5138841c2b4f21f871eaa8f138c6ac4c46f28899f9",
"sha256:57be98177ce784495dff53f40620995ad0a56456246ed9d51977e595de58e12e",
"sha256:36ffb216e2f361a5a0a7e219aea6cd44da11c64061baed273944aae21223186c",
"sha256:d0dc3e5737adcc9a23fd3d3d3072b887fefb48143309563f412ef7b0ebdfdb30",
"sha256:7769ac9203ebe6d8db16904c54d57d77360fcc1926ed7afaa86b04050e4afa5b",
"sha256:4c21d7304d37715e6aed756e4d0c374c99c9bb1fa8d64f546b95474b17ac23de",
"sha256:0aa44ffdeaaf6ba45d61980bb2c07e87d4dcac7a8b5b9d458124bc1adcda5233",
"sha256:71ac6dac6835de75aaf531cae9ffa447dae0783ba1f43bf6eaccfad3680a5b9c",
"sha256:88583c6565c9299f617238a500f1a47510bac54daff7872d6a343f13361b659e",
"sha256:2812bc45a7f53f366217b76a1c53e6728fbfa7f7524d16a321ea8f7131428bd1",
"sha256:124a9d529eec5e10f307eb237df3efc43dd1fb7ebdb5da5e480c4ed372648b6b",
"sha256:f04b184984c23e0caac3c55eac2fe2dbb88726a5a1b35e23715eff6f29a4705c",
"sha256:d06260e6102b2f18dbee3736185cd6a2e1c88c0fad782bf8e9d7a7a1b24e02b0",
"sha256:0cddc6cde79e1932efc71d9974a4418184ad0b8ca46c633ad772b2c5eaf36b3c",
"sha256:9e08918b744b89d30750eca8598f37ae75b16202870db678fde970d85afed3e3",
"sha256:b46f31e806f6884bd1053ad1d78ecaca6d1bc5dd94a1b783a6ff0bb4b3a60962",
"sha256:dd98d4f88ce0abda2b02c1542d1de22dd342023f3ba09874bd95841283f29433",
"sha256:8f52c4c8f1cf15419193026e731f34a3260a3ce7977b875ba1eb2517b8a3f660",
"sha256:c18f316cad969111b1ff9e84c82fbc9ae6f25f35701118182d384585940cdf80",
"sha256:95b82fdfdaac71640b281da6b9a2c3700177ba5190a786881b184de744ad55de",
"sha256:cef79715f2335bfc1ef7082bcb8b2bac87271431653455221a9127fde146208c",
"sha256:4626d699551f66687e5f7e7f9b79bfce611e12edebfb9fec276e2df8ec46541e",
"sha256:cf63f590090404c52f179b7ceacb7cd549de3a1697bcfe2f79be180b2801d109",
"sha256:7d96fbb5f23a62300aa9bef7d286cd61aca8902357619c8708c0290aba5df73f"
"sha256:65a272821d5d8194358d6b46f3ca727fa56a6b63981606eac737c86d27309cdd",
"sha256:abbd2fb4a5a04c11b5e04eb146659a0cf67bb237dd3d7ca3b9994d3a9f826e55",
"sha256:3682a17fbf72d56d7e46db2e80ca23850b79c28cfe75dcd9b82f58808f730909",
"sha256:75322a531504d4f383264391d89993a42e286da8821ddc5ac315e57305cb84f0",
"sha256:01c45df6d90497c20aa2a07789a41941f9a1029faa30bf725fc7f6d515b1afe9",
"sha256:34d49d0f72dd82b9530322c48b70ac78cca0911275da741c3b1d2f3603c5f295",
"sha256:1b46f37927fa6cd1f3fe34b54f1a23bd5bea1d905657289e08e1297069a1a597",
"sha256:5b653c9379ce29ce271fbe1010c5396670f018e78b643e21beefbb3dc6d291de",
"sha256:3cf2830b9a6ad7f6e965fa53a768d4d2372a7856f20ffa6ce43d2fe9c0d34b19",
"sha256:1b164bba1320b14905dcff77da10d5ce9c411ac4acc4fb4ed9a2a4d10fae38c9",
"sha256:c557ad647facb3c0027a9d0af58853f905e85a0a2f04dcb73f8e665272fcdc3a",
"sha256:6b6379495d3baacf7ed755ac68547c8dff6ce5d37bf370f0b7678888dc1283f9",
"sha256:8523fbde9c2216f3f2b950cb01ebe52e785eaa8a07ffeb456dd3576ca1b4fb9b",
"sha256:a7182ea298cc3555ea56ffbb0748fe0d5e0d81451e2bc16d7f4645cd01b1ca70",
"sha256:7f457cbda964257f443bac861d3a36732dcba8183149e7818ee2fb7c86901b94",
"sha256:0c9fef4f8d444e337df96c54544aeb85b7215b2ed7483bb6c35de97ac99f1bcd",
"sha256:7ff1fc76d8804e0f870c343a72007ff587090c218b0f92d8ee784ac2b6eaf5b9",
"sha256:accc9f6b77bed0a6f267b4fae120f6008a951193d548cdbe9b61fc98a08b1cf8",
"sha256:bd88c8ce0d1504fdfd96a35911dd4f3edfb2e560d7cfdb5a3d09aa571ae5fbae",
"sha256:defabb7fbb99f9f7b3e0b24b286a46855caef4776495211b066e9e6592d12b04",
"sha256:231047b05907315ae9a9b6925751f9fd2c479cf7b100fff62485a25e382ca0d4",
"sha256:0e7996e9b46b4d8b4ac1c329a00e2d10edcd8380b95d2a676fccabf4c1dd0512",
"sha256:691f2cd97cf026c611df1ea5055755eec7f878f2d4f4330dc8686583de6fc5fd",
"sha256:1858b1933d483ec5727549d3fe166eeb54229fbd6a9d3d7ea26d2c8a28048058",
"sha256:0e3cd94c95d30ba9ca3cff40e9b2a14e1a10a4fd8131105b86c6b61648f57e4b",
"sha256:28f0c6652c1b130f1e576b60532f84b19379485eb8da6185c29bd8c9c9bc97bf",
"sha256:8f37627f16e026523fca326f1b5c9a43534862fede6c3e99c2ba6a776d75c1ab",
"sha256:e2629cdbcad82b83922a3488937632a4983ecc0fed3e5cfbf430d069382eeb9b"
],
"version": "==4.2.0"
"version": "==4.2.1"
},
"markdown": {
"hashes": [
@ -373,6 +379,14 @@
],
"version": "==0.5.0"
},
"more-itertools": {
"hashes": [
"sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e",
"sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea",
"sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"
],
"version": "==4.1.0"
},
"msgpack-python": {
"hashes": [
"sha256:378cc8a6d3545b532dfd149da715abae4fda2a3adb6d74e525d0d5e51f46909b"
@ -465,10 +479,10 @@
},
"py": {
"hashes": [
"sha256:8cca5c229d225f8c1e3085be4fcf306090b00850fefad892f9d96c7b6e2f310f",
"sha256:ca18943e28235417756316bfada6cd96b23ce60dd532642690dcfdaba988a76d"
"sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a",
"sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881"
],
"version": "==1.5.2"
"version": "==1.5.3"
},
"pycparser": {
"hashes": [
@ -506,10 +520,10 @@
},
"pytest": {
"hashes": [
"sha256:062027955bccbc04d2fcd5d79690947e018ba31abe4c90b2c6721abec734261b",
"sha256:117bad36c1a787e1a8a659df35de53ba05f9f3398fb9e4ac17e80ad5903eb8c5"
"sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c",
"sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"
],
"version": "==3.4.2"
"version": "==3.5.0"
},
"pytest-django": {
"hashes": [

View File

@ -72,6 +72,7 @@ INSTALLED_APPS = [
'analytical',
'annoying',
'compressor',
'computed_property',
'corsheaders',
'debug_toolbar',
'django_activeurl',

View File

@ -36,6 +36,7 @@ maps = {'sitemaps': sections}
urlpatterns = [
url('', include('home.urls')),
url('', include('entries.urls')),
url('', include('users.urls')),
url('^.well-known/', include('wellknowns.urls')),
url('^admin/', otp_admin_site.urls),
url('^auth/', include('lemonauth.urls')),

View File

@ -0,0 +1,24 @@
# Generated by Django 2.0.3 on 2018-03-23 01:00
import computed_property.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0012_auto_20180129_1614'),
]
operations = [
migrations.AddField(
model_name='user',
name='email_md5',
field=computed_property.fields.ComputedCharField(compute_from='calc_email_md5', default='', editable=False, max_length=32, unique=True),
),
migrations.AddField(
model_name='user',
name='email_sha256',
field=computed_property.fields.ComputedCharField(compute_from='calc_email_sha256', default='', editable=False, max_length=64, unique=True),
),
]

View File

@ -1,7 +1,9 @@
from computed_property import ComputedCharField
from django.db import models
from django.contrib.auth.models import AbstractUser, UserManager as DjangoUserManager
from django.contrib.sites.models import Site as DjangoSite
from django.utils.functional import cached_property
from hashlib import md5, sha256
from meta.models import ModelMeta
from urllib.parse import urljoin
from lemoncurry import utils
@ -46,6 +48,21 @@ class User(ModelMeta, AbstractUser):
# This is gonna need to change if I ever decide to add multiple-user support ;)
url = '/'
email_md5 = ComputedCharField(
compute_from='calc_email_md5', max_length=32, unique=True
)
email_sha256 = ComputedCharField(
compute_from='calc_email_sha256', max_length=64, unique=True
)
@property
def calc_email_md5(self):
return md5(self.email.lower().encode('utf-8')).hexdigest()
@property
def calc_email_sha256(self):
return sha256(self.email.lower().encode('utf-8')).hexdigest()
@property
def name(self):
return '{0} {1}'.format(self.first_name, self.last_name)

8
users/urls.py Normal file
View File

@ -0,0 +1,8 @@
from django.conf.urls import url
from .views import libravatar
app_name = 'users'
urlpatterns = (
url('^avatar/(?P<hash>[a-z0-9]+)$', libravatar, name='libravatar'),
)

49
users/views.py Normal file
View File

@ -0,0 +1,49 @@
from django.http import HttpResponse, HttpResponseRedirect
from PIL import Image
from lemoncurry import utils
from .models import User
def try_libravatar_org(hash, get):
url = 'https://seccdn.libravatar.org/avatar/' + hash
if get:
url += '?' + get.urlencode()
return HttpResponseRedirect(url)
def libravatar(request, hash):
g = request.GET
size = g.get('s', g.get('size', 80))
try:
size = int(size)
except ValueError:
return utils.bad_req('size parameter must be an integer')
if not 1 <= size <= 128:
return utils.bad_req('size parameter must be between 1 and 128')
if len(hash) == 32:
where = {'email_md5': hash}
elif len(hash) == 64:
where = {'email_sha256': hash}
else:
return utils.bad_req('hash must be either md5 or sha256')
# If the user doesn't exist or lacks an avatar, see if libravatar.org has
# one for them - libravatar.org falls back to Gravatar when possible (only
# for MD5 hashes, since Gravatar doesn't support SHA-256), so this ensures
# all the most likely places are checked.
try:
user = User.objects.get(**where)
except User.DoesNotExist:
return try_libravatar_org(hash, g)
if not user.avatar:
return try_libravatar_org(hash, g)
im = Image.open(user.avatar)
im_resized = im.resize((size, size))
response = HttpResponse(content_type='image/'+im.format.lower())
im_resized.save(response, im.format)
return response