Browse Source

Add support for serving users' avatars through the Libravatar API

tags/v1.9.5
Danielle McLean 1 year ago
parent
commit
43348a89da
Signed by: Danielle McLean <dani@00dani.me> GPG Key ID: 5A5D2D1AFF12EEC5
8 changed files with 153 additions and 38 deletions
  1. 1
    0
      Pipfile
  2. 52
    38
      Pipfile.lock
  3. 1
    0
      lemoncurry/settings/base.py
  4. 1
    0
      lemoncurry/urls.py
  5. 24
    0
      users/migrations/0013_auto_20180323_1200.py
  6. 17
    0
      users/models.py
  7. 8
    0
      users/urls.py
  8. 49
    0
      users/views.py

+ 1
- 0
Pipfile View File

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

+ 52
- 38
Pipfile.lock View File

@@ -1,7 +1,7 @@
1 1
 {
2 2
     "_meta": {
3 3
         "hash": {
4
-            "sha256": "a824f0af4df1d0088d5dd66db92329358a5cf0095c71f1cb59f6e60583b912f5"
4
+            "sha256": "0b68c7a2821e8063bc9d7bae759a9d8dd57785e341e6d23d6421ad93310c7d1b"
5 5
         },
6 6
         "host-environment-markers": {
7 7
             "implementation_name": "cpython",
@@ -11,7 +11,7 @@
11 11
             "platform_python_implementation": "CPython",
12 12
             "platform_release": "17.5.0",
13 13
             "platform_system": "Darwin",
14
-            "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",
14
+            "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",
15 15
             "python_full_version": "3.6.4",
16 16
             "python_version": "3.6",
17 17
             "sys_platform": "darwin"
@@ -206,6 +206,12 @@
206 206
             ],
207 207
             "version": "==2.2"
208 208
         },
209
+        "django-computed-property": {
210
+            "hashes": [
211
+                "sha256:31aa6453a5c504ce196ba9ae3bacbe0557cadf7ae89e25431b90bf206febd3b3"
212
+            ],
213
+            "version": "==0.2.1"
214
+        },
209 215
         "django-cors-headers": {
210 216
             "hashes": [
211 217
                 "sha256:0e9532628b3aa8806442d4d0b15e56112e6cfbef3735e13401935c98b842a2b4",
@@ -323,36 +329,36 @@
323 329
         },
324 330
         "lxml": {
325 331
             "hashes": [
326
-                "sha256:0af9c9267b1257319d49e9c1e9abbf92a99f965bee3c4733e0f0f7578985182d",
327
-                "sha256:62bfcd0629991e1c1257ffd28df2ab31a5c44da4c06823c26ec0f472723a84ca",
328
-                "sha256:1d1e45584353e4d563685874707fc8c85cdd11b0ef3b79d77bb38046134d68a9",
329
-                "sha256:29697224b2df76edf7c2de9bcd90a26dd28fe85c5fd7f0171cae84f8383b227e",
330
-                "sha256:988d55112f196e12341b7c5138841c2b4f21f871eaa8f138c6ac4c46f28899f9",
331
-                "sha256:57be98177ce784495dff53f40620995ad0a56456246ed9d51977e595de58e12e",
332
-                "sha256:36ffb216e2f361a5a0a7e219aea6cd44da11c64061baed273944aae21223186c",
333
-                "sha256:d0dc3e5737adcc9a23fd3d3d3072b887fefb48143309563f412ef7b0ebdfdb30",
334
-                "sha256:7769ac9203ebe6d8db16904c54d57d77360fcc1926ed7afaa86b04050e4afa5b",
335
-                "sha256:4c21d7304d37715e6aed756e4d0c374c99c9bb1fa8d64f546b95474b17ac23de",
336
-                "sha256:0aa44ffdeaaf6ba45d61980bb2c07e87d4dcac7a8b5b9d458124bc1adcda5233",
337
-                "sha256:71ac6dac6835de75aaf531cae9ffa447dae0783ba1f43bf6eaccfad3680a5b9c",
338
-                "sha256:88583c6565c9299f617238a500f1a47510bac54daff7872d6a343f13361b659e",
339
-                "sha256:2812bc45a7f53f366217b76a1c53e6728fbfa7f7524d16a321ea8f7131428bd1",
340
-                "sha256:124a9d529eec5e10f307eb237df3efc43dd1fb7ebdb5da5e480c4ed372648b6b",
341
-                "sha256:f04b184984c23e0caac3c55eac2fe2dbb88726a5a1b35e23715eff6f29a4705c",
342
-                "sha256:d06260e6102b2f18dbee3736185cd6a2e1c88c0fad782bf8e9d7a7a1b24e02b0",
343
-                "sha256:0cddc6cde79e1932efc71d9974a4418184ad0b8ca46c633ad772b2c5eaf36b3c",
344
-                "sha256:9e08918b744b89d30750eca8598f37ae75b16202870db678fde970d85afed3e3",
345
-                "sha256:b46f31e806f6884bd1053ad1d78ecaca6d1bc5dd94a1b783a6ff0bb4b3a60962",
346
-                "sha256:dd98d4f88ce0abda2b02c1542d1de22dd342023f3ba09874bd95841283f29433",
347
-                "sha256:8f52c4c8f1cf15419193026e731f34a3260a3ce7977b875ba1eb2517b8a3f660",
348
-                "sha256:c18f316cad969111b1ff9e84c82fbc9ae6f25f35701118182d384585940cdf80",
349
-                "sha256:95b82fdfdaac71640b281da6b9a2c3700177ba5190a786881b184de744ad55de",
350
-                "sha256:cef79715f2335bfc1ef7082bcb8b2bac87271431653455221a9127fde146208c",
351
-                "sha256:4626d699551f66687e5f7e7f9b79bfce611e12edebfb9fec276e2df8ec46541e",
352
-                "sha256:cf63f590090404c52f179b7ceacb7cd549de3a1697bcfe2f79be180b2801d109",
353
-                "sha256:7d96fbb5f23a62300aa9bef7d286cd61aca8902357619c8708c0290aba5df73f"
354
-            ],
355
-            "version": "==4.2.0"
332
+                "sha256:65a272821d5d8194358d6b46f3ca727fa56a6b63981606eac737c86d27309cdd",
333
+                "sha256:abbd2fb4a5a04c11b5e04eb146659a0cf67bb237dd3d7ca3b9994d3a9f826e55",
334
+                "sha256:3682a17fbf72d56d7e46db2e80ca23850b79c28cfe75dcd9b82f58808f730909",
335
+                "sha256:75322a531504d4f383264391d89993a42e286da8821ddc5ac315e57305cb84f0",
336
+                "sha256:01c45df6d90497c20aa2a07789a41941f9a1029faa30bf725fc7f6d515b1afe9",
337
+                "sha256:34d49d0f72dd82b9530322c48b70ac78cca0911275da741c3b1d2f3603c5f295",
338
+                "sha256:1b46f37927fa6cd1f3fe34b54f1a23bd5bea1d905657289e08e1297069a1a597",
339
+                "sha256:5b653c9379ce29ce271fbe1010c5396670f018e78b643e21beefbb3dc6d291de",
340
+                "sha256:3cf2830b9a6ad7f6e965fa53a768d4d2372a7856f20ffa6ce43d2fe9c0d34b19",
341
+                "sha256:1b164bba1320b14905dcff77da10d5ce9c411ac4acc4fb4ed9a2a4d10fae38c9",
342
+                "sha256:c557ad647facb3c0027a9d0af58853f905e85a0a2f04dcb73f8e665272fcdc3a",
343
+                "sha256:6b6379495d3baacf7ed755ac68547c8dff6ce5d37bf370f0b7678888dc1283f9",
344
+                "sha256:8523fbde9c2216f3f2b950cb01ebe52e785eaa8a07ffeb456dd3576ca1b4fb9b",
345
+                "sha256:a7182ea298cc3555ea56ffbb0748fe0d5e0d81451e2bc16d7f4645cd01b1ca70",
346
+                "sha256:7f457cbda964257f443bac861d3a36732dcba8183149e7818ee2fb7c86901b94",
347
+                "sha256:0c9fef4f8d444e337df96c54544aeb85b7215b2ed7483bb6c35de97ac99f1bcd",
348
+                "sha256:7ff1fc76d8804e0f870c343a72007ff587090c218b0f92d8ee784ac2b6eaf5b9",
349
+                "sha256:accc9f6b77bed0a6f267b4fae120f6008a951193d548cdbe9b61fc98a08b1cf8",
350
+                "sha256:bd88c8ce0d1504fdfd96a35911dd4f3edfb2e560d7cfdb5a3d09aa571ae5fbae",
351
+                "sha256:defabb7fbb99f9f7b3e0b24b286a46855caef4776495211b066e9e6592d12b04",
352
+                "sha256:231047b05907315ae9a9b6925751f9fd2c479cf7b100fff62485a25e382ca0d4",
353
+                "sha256:0e7996e9b46b4d8b4ac1c329a00e2d10edcd8380b95d2a676fccabf4c1dd0512",
354
+                "sha256:691f2cd97cf026c611df1ea5055755eec7f878f2d4f4330dc8686583de6fc5fd",
355
+                "sha256:1858b1933d483ec5727549d3fe166eeb54229fbd6a9d3d7ea26d2c8a28048058",
356
+                "sha256:0e3cd94c95d30ba9ca3cff40e9b2a14e1a10a4fd8131105b86c6b61648f57e4b",
357
+                "sha256:28f0c6652c1b130f1e576b60532f84b19379485eb8da6185c29bd8c9c9bc97bf",
358
+                "sha256:8f37627f16e026523fca326f1b5c9a43534862fede6c3e99c2ba6a776d75c1ab",
359
+                "sha256:e2629cdbcad82b83922a3488937632a4983ecc0fed3e5cfbf430d069382eeb9b"
360
+            ],
361
+            "version": "==4.2.1"
356 362
         },
357 363
         "markdown": {
358 364
             "hashes": [
@@ -373,6 +379,14 @@
373 379
             ],
374 380
             "version": "==0.5.0"
375 381
         },
382
+        "more-itertools": {
383
+            "hashes": [
384
+                "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e",
385
+                "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea",
386
+                "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"
387
+            ],
388
+            "version": "==4.1.0"
389
+        },
376 390
         "msgpack-python": {
377 391
             "hashes": [
378 392
                 "sha256:378cc8a6d3545b532dfd149da715abae4fda2a3adb6d74e525d0d5e51f46909b"
@@ -465,10 +479,10 @@
465 479
         },
466 480
         "py": {
467 481
             "hashes": [
468
-                "sha256:8cca5c229d225f8c1e3085be4fcf306090b00850fefad892f9d96c7b6e2f310f",
469
-                "sha256:ca18943e28235417756316bfada6cd96b23ce60dd532642690dcfdaba988a76d"
482
+                "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a",
483
+                "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881"
470 484
             ],
471
-            "version": "==1.5.2"
485
+            "version": "==1.5.3"
472 486
         },
473 487
         "pycparser": {
474 488
             "hashes": [
@@ -506,10 +520,10 @@
506 520
         },
507 521
         "pytest": {
508 522
             "hashes": [
509
-                "sha256:062027955bccbc04d2fcd5d79690947e018ba31abe4c90b2c6721abec734261b",
510
-                "sha256:117bad36c1a787e1a8a659df35de53ba05f9f3398fb9e4ac17e80ad5903eb8c5"
523
+                "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c",
524
+                "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"
511 525
             ],
512
-            "version": "==3.4.2"
526
+            "version": "==3.5.0"
513 527
         },
514 528
         "pytest-django": {
515 529
             "hashes": [

+ 1
- 0
lemoncurry/settings/base.py View File

@@ -72,6 +72,7 @@ INSTALLED_APPS = [
72 72
     'analytical',
73 73
     'annoying',
74 74
     'compressor',
75
+    'computed_property',
75 76
     'corsheaders',
76 77
     'debug_toolbar',
77 78
     'django_activeurl',

+ 1
- 0
lemoncurry/urls.py View File

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

+ 24
- 0
users/migrations/0013_auto_20180323_1200.py View File

@@ -0,0 +1,24 @@
1
+# Generated by Django 2.0.3 on 2018-03-23 01:00
2
+
3
+import computed_property.fields
4
+from django.db import migrations, models
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('users', '0012_auto_20180129_1614'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='user',
16
+            name='email_md5',
17
+            field=computed_property.fields.ComputedCharField(compute_from='calc_email_md5', default='', editable=False, max_length=32, unique=True),
18
+        ),
19
+        migrations.AddField(
20
+            model_name='user',
21
+            name='email_sha256',
22
+            field=computed_property.fields.ComputedCharField(compute_from='calc_email_sha256', default='', editable=False, max_length=64, unique=True),
23
+        ),
24
+    ]

+ 17
- 0
users/models.py View File

@@ -1,7 +1,9 @@
1
+from computed_property import ComputedCharField
1 2
 from django.db import models
2 3
 from django.contrib.auth.models import AbstractUser, UserManager as DjangoUserManager
3 4
 from django.contrib.sites.models import Site as DjangoSite
4 5
 from django.utils.functional import cached_property
6
+from hashlib import md5, sha256
5 7
 from meta.models import ModelMeta
6 8
 from urllib.parse import urljoin
7 9
 from lemoncurry import utils
@@ -46,6 +48,21 @@ class User(ModelMeta, AbstractUser):
46 48
     # This is gonna need to change if I ever decide to add multiple-user support ;)
47 49
     url = '/'
48 50
 
51
+    email_md5 = ComputedCharField(
52
+        compute_from='calc_email_md5', max_length=32, unique=True
53
+    )
54
+    email_sha256 = ComputedCharField(
55
+        compute_from='calc_email_sha256', max_length=64, unique=True
56
+    )
57
+
58
+    @property
59
+    def calc_email_md5(self):
60
+        return md5(self.email.lower().encode('utf-8')).hexdigest()
61
+
62
+    @property
63
+    def calc_email_sha256(self):
64
+        return sha256(self.email.lower().encode('utf-8')).hexdigest()
65
+
49 66
     @property
50 67
     def name(self):
51 68
         return '{0} {1}'.format(self.first_name, self.last_name)

+ 8
- 0
users/urls.py View File

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

+ 49
- 0
users/views.py View File

@@ -0,0 +1,49 @@
1
+from django.http import HttpResponse, HttpResponseRedirect
2
+from PIL import Image
3
+
4
+from lemoncurry import utils
5
+from .models import User
6
+
7
+
8
+def try_libravatar_org(hash, get):
9
+    url = 'https://seccdn.libravatar.org/avatar/' + hash
10
+    if get:
11
+        url += '?' + get.urlencode()
12
+    return HttpResponseRedirect(url)
13
+
14
+
15
+def libravatar(request, hash):
16
+    g = request.GET
17
+    size = g.get('s', g.get('size', 80))
18
+    try:
19
+        size = int(size)
20
+    except ValueError:
21
+        return utils.bad_req('size parameter must be an integer')
22
+    if not 1 <= size <= 128:
23
+        return utils.bad_req('size parameter must be between 1 and 128')
24
+
25
+    if len(hash) == 32:
26
+        where = {'email_md5': hash}
27
+    elif len(hash) == 64:
28
+        where = {'email_sha256': hash}
29
+    else:
30
+        return utils.bad_req('hash must be either md5 or sha256')
31
+
32
+    # If the user doesn't exist or lacks an avatar, see if libravatar.org has
33
+    # one for them - libravatar.org falls back to Gravatar when possible (only
34
+    # for MD5 hashes, since Gravatar doesn't support SHA-256), so this ensures
35
+    # all the most likely places are checked.
36
+    try:
37
+        user = User.objects.get(**where)
38
+    except User.DoesNotExist:
39
+        return try_libravatar_org(hash, g)
40
+
41
+    if not user.avatar:
42
+        return try_libravatar_org(hash, g)
43
+
44
+    im = Image.open(user.avatar)
45
+    im_resized = im.resize((size, size))
46
+
47
+    response = HttpResponse(content_type='image/'+im.format.lower())
48
+    im_resized.save(response, im.format)
49
+    return response

Loading…
Cancel
Save