diff --git a/config.py b/config.py index fecce2d..598182c 100644 --- a/config.py +++ b/config.py @@ -1,4 +1,4 @@ -''' +''' yes Configuration overrides for settings.py ''' @@ -52,7 +52,7 @@ OPENID_CREATE_USERS = True OPENID_UPDATE_DETAILS_FROM_SREG = True SITE_NAME = os.environ.get('SITE_NAME', 'libravatar') -IVATAR_VERSION = '1.2' +IVATAR_VERSION = '1.3' SECURE_BASE_URL = os.environ.get('SECURE_BASE_URL', 'https://avatars.linux-kernel.at/avatar/') BASE_URL = os.environ.get('BASE_URL', 'http://avatars.linux-kernel.at/avatar/') diff --git a/ivatar/ivataraccount/migrations/0015_auto_20200225_0934.py b/ivatar/ivataraccount/migrations/0015_auto_20200225_0934.py new file mode 100644 index 0000000..9644afe --- /dev/null +++ b/ivatar/ivataraccount/migrations/0015_auto_20200225_0934.py @@ -0,0 +1,28 @@ +# Generated by Django 3.0.3 on 2020-02-25 09:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ivataraccount', '0014_auto_20190218_1602'), + ] + + operations = [ + migrations.AddField( + model_name='confirmedopenid', + name='alt_digest1', + field=models.CharField(blank=True, default=None, max_length=64, null=True), + ), + migrations.AddField( + model_name='confirmedopenid', + name='alt_digest2', + field=models.CharField(blank=True, default=None, max_length=64, null=True), + ), + migrations.AddField( + model_name='confirmedopenid', + name='alt_digest3', + field=models.CharField(blank=True, default=None, max_length=64, null=True), + ), + ] diff --git a/ivatar/ivataraccount/models.py b/ivatar/ivataraccount/models.py index e8c1ffc..14aaa39 100644 --- a/ivatar/ivataraccount/models.py +++ b/ivatar/ivataraccount/models.py @@ -35,6 +35,7 @@ from ivatar.settings import MAX_LENGTH_EMAIL, logger from ivatar.settings import MAX_PIXELS, AVATAR_MAX_SIZE, JPEG_QUALITY from ivatar.settings import MAX_LENGTH_URL from ivatar.settings import SECURE_BASE_URL, SITE_NAME, DEFAULT_FROM_EMAIL +from ivatar.utils import openid_variations from .gravatar import get_photo as get_gravatar_photo from ivatar.utils import random_string @@ -469,7 +470,15 @@ class ConfirmedOpenId(BaseAccountModel): null=True, on_delete=models.deletion.SET_NULL, ) + # http:/// base version - http w/ trailing slash digest = models.CharField(max_length=64) + # http:// - http w/o trailing slash + alt_digest1 = models.CharField(max_length=64, null=True, blank=True, default=None) + # https:/// - https w/ trailing slash + alt_digest2 = models.CharField(max_length=64, null=True, blank=True, default=None) + # https:// - https w/o trailing slash + alt_digest3 = models.CharField(max_length=64, null=True, blank=True, default=None) + access_count = models.BigIntegerField(default=0, editable=False) class Meta: # pylint: disable=too-few-public-methods @@ -497,10 +506,13 @@ class ConfirmedOpenId(BaseAccountModel): lowercase_url = urlunsplit( (url.scheme.lower(), netloc, url.path, url.query, url.fragment) ) - #if lowercase_url[-1] != '/': - # lowercase_url += '/' self.openid = lowercase_url - self.digest = hashlib.sha256(lowercase_url.encode('utf-8')).hexdigest() + + self.digest = hashlib.sha256(openid_variations(lowercase_url)[0].encode('utf-8')).hexdigest() + self.alt_digest1 = hashlib.sha256(openid_variations(lowercase_url)[1].encode('utf-8')).hexdigest() + self.alt_digest2 = hashlib.sha256(openid_variations(lowercase_url)[2].encode('utf-8')).hexdigest() + self.alt_digest3 = hashlib.sha256(openid_variations(lowercase_url)[3].encode('utf-8')).hexdigest() + return super().save(force_insert, force_update, using, update_fields) def __str__(self): diff --git a/ivatar/test_utils.py b/ivatar/test_utils.py new file mode 100644 index 0000000..773a2fa --- /dev/null +++ b/ivatar/test_utils.py @@ -0,0 +1,46 @@ +''' +Test our utils from ivatar.utils +''' + +from django.test import TestCase + +from ivatar.utils import openid_variations + + +class Tester(TestCase): + ''' + Main test class + ''' + + def test_openid_variations(self): + ''' + Test if the OpenID variation "generator" does the correct thing + ''' + openid0 = 'http://user.url/' + openid1 = 'http://user.url' + openid2 = 'https://user.url/' + openid3 = 'https://user.url' + + # First variation + self.assertEqual(openid_variations(openid0)[0], openid0) + self.assertEqual(openid_variations(openid0)[1], openid1) + self.assertEqual(openid_variations(openid0)[2], openid2) + self.assertEqual(openid_variations(openid0)[3], openid3) + + # Second varitations + self.assertEqual(openid_variations(openid1)[0], openid0) + self.assertEqual(openid_variations(openid1)[1], openid1) + self.assertEqual(openid_variations(openid1)[2], openid2) + self.assertEqual(openid_variations(openid1)[3], openid3) + + # Third varitations + self.assertEqual(openid_variations(openid2)[0], openid0) + self.assertEqual(openid_variations(openid2)[1], openid1) + self.assertEqual(openid_variations(openid2)[2], openid2) + self.assertEqual(openid_variations(openid2)[3], openid3) + + # Forth varitations + self.assertEqual(openid_variations(openid3)[0], openid0) + self.assertEqual(openid_variations(openid3)[1], openid1) + self.assertEqual(openid_variations(openid3)[2], openid2) + self.assertEqual(openid_variations(openid3)[3], openid3) diff --git a/ivatar/utils.py b/ivatar/utils.py index eb391d1..877412d 100644 --- a/ivatar/utils.py +++ b/ivatar/utils.py @@ -11,3 +11,25 @@ def random_string(length=10): ''' return ''.join(random.SystemRandom().choice( string.ascii_lowercase + string.digits) for _ in range(length)) + + +def openid_variations(openid): + ''' + Return the various OpenID variations, ALWAYS in the same order: + - http w/ trailing slash + - http w/o trailing slash + - https w/ trailing slash + - https w/o trailing slash + ''' + + # Make the 'base' version: http w/ trailing slash + if openid.startswith('https://'): + openid = openid.replace('https://', 'http://') + if openid[-1] != '/': + openid = openid + '/' + + # http w/o trailing slash + var1 = openid[0:-1] + var2 = openid.replace('http://', 'https://') + var3 = var2[0:-1] + return (openid, var1, var2, var3) diff --git a/ivatar/views.py b/ivatar/views.py index db537b8..cc053fd 100644 --- a/ivatar/views.py +++ b/ivatar/views.py @@ -15,6 +15,8 @@ from django.urls import reverse_lazy from django.contrib import messages from simplecrypt import decrypt from binascii import unhexlify +from django.db.models import Q + from PIL import Image from monsterid.id import build_monster as BuildMonster @@ -127,7 +129,15 @@ class AvatarImageView(TemplateView): except ObjectDoesNotExist: model = ConfirmedOpenId try: - obj = model.objects.get(digest=kwargs['digest']) + d = kwargs['digest'] + # OpenID is tricky. http vs. https, versus trailing slash or not + # However, some users eventually have added their variations already + # and therfore we need to use filter() and first() + obj = model.objects.filter( + Q(digest=d) | + Q(alt_digest1=d) | + Q(alt_digest2=d) | + Q(alt_digest3=d)).first() except: pass