First preparations for Django >= 4.x

This commit is contained in:
Oliver Falk
2022-02-14 08:41:21 +00:00
parent 6e084ea080
commit a9cff27ef7
23 changed files with 617 additions and 523 deletions

View File

@@ -6,7 +6,7 @@ Configuration overrides for settings.py
import os import os
import sys import sys
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.contrib.messages import constants as message_constants from django.contrib.messages import constants as message_constants
from ivatar.settings import BASE_DIR from ivatar.settings import BASE_DIR

View File

@@ -1,7 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
''' # -*- coding: utf-8 -*-
"""
Import the whole libravatar export Import the whole libravatar export
''' """
import os import os
from os.path import isfile, isdir, join from os.path import isfile, isdir, join
@@ -9,13 +10,18 @@ import sys
import base64 import base64
from io import BytesIO from io import BytesIO
import django import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ivatar.settings") # pylint: disable=wrong-import-position
os.environ.setdefault(
"DJANGO_SETTINGS_MODULE", "ivatar.settings"
) # pylint: disable=wrong-import-position
django.setup() # pylint: disable=wrong-import-position django.setup() # pylint: disable=wrong-import-position
from django.contrib.auth.models import User from django.contrib.auth.models import User
from PIL import Image from PIL import Image
from django_openid_auth.models import UserOpenID from django_openid_auth.models import UserOpenID
from ivatar.settings import JPEG_QUALITY from ivatar.settings import JPEG_QUALITY
from ivatar.ivataraccount.read_libravatar_export import read_gzdata as libravatar_read_gzdata from ivatar.ivataraccount.read_libravatar_export import (
read_gzdata as libravatar_read_gzdata,
)
from ivatar.ivataraccount.models import ConfirmedEmail from ivatar.ivataraccount.models import ConfirmedEmail
from ivatar.ivataraccount.models import ConfirmedOpenId from ivatar.ivataraccount.models import ConfirmedOpenId
from ivatar.ivataraccount.models import Photo from ivatar.ivataraccount.models import Photo
@@ -26,54 +32,63 @@ if len(sys.argv) < 2:
exit(-255) exit(-255)
if not isdir(sys.argv[1]): if not isdir(sys.argv[1]):
print("First argument to '%s' must be a directory containing the exports" % sys.argv[0]) print(
"First argument to '%s' must be a directory containing the exports"
% sys.argv[0]
)
exit(-255) exit(-255)
PATH = sys.argv[1] PATH = sys.argv[1]
for file in os.listdir(PATH): for file in os.listdir(PATH):
if not file.endswith('.xml.gz'): if not file.endswith(".xml.gz"):
continue continue
if isfile(join(PATH, file)): if isfile(join(PATH, file)):
fh = open(join(PATH, file), 'rb') fh = open(join(PATH, file), "rb")
items = libravatar_read_gzdata(fh.read()) items = libravatar_read_gzdata(fh.read())
print('Adding user "%s"' % items['username']) print('Adding user "%s"' % items["username"])
(user, created) = User.objects.get_or_create(username=items['username']) (user, created) = User.objects.get_or_create(username=items["username"])
user.password = items['password'] user.password = items["password"]
user.save() user.save()
saved_photos = {} saved_photos = {}
for photo in items['photos']: for photo in items["photos"]:
photo_id = photo['id'] photo_id = photo["id"]
data = base64.decodebytes(bytes(photo['data'], 'utf-8')) data = base64.decodebytes(bytes(photo["data"], "utf-8"))
pilobj = Image.open(BytesIO(data)) pilobj = Image.open(BytesIO(data))
out = BytesIO() out = BytesIO()
pilobj.save(out, pilobj.format, quality=JPEG_QUALITY) pilobj.save(out, pilobj.format, quality=JPEG_QUALITY)
out.seek(0) out.seek(0)
photo = Photo() photo = Photo()
photo.user = user photo.user = user
photo.ip_address = '0.0.0.0' photo.ip_address = "0.0.0.0"
photo.format = file_format(pilobj.format) photo.format = file_format(pilobj.format)
photo.data = out.read() photo.data = out.read()
photo.save() photo.save()
saved_photos[photo_id] = photo saved_photos[photo_id] = photo
for email in items['emails']: for email in items["emails"]:
try: try:
ConfirmedEmail.objects.get_or_create(email=email['email'], user=user, ConfirmedEmail.objects.get_or_create(
photo=saved_photos.get(email['photo_id'])) email=email["email"],
except django.db.utils.IntegrityError: user=user,
print('%s not unique?' % email['email']) photo=saved_photos.get(email["photo_id"]),
for openid in items['openids']:
try:
ConfirmedOpenId.objects.get_or_create(openid=openid['openid'], user=user,
photo=saved_photos.get(openid['photo_id'])) # pylint: disable=no-member
UserOpenID.objects.get_or_create(
user_id=user.id,
claimed_id=openid['openid'],
display_id=openid['openid'],
) )
except django.db.utils.IntegrityError: except django.db.utils.IntegrityError:
print('%s not unique?' % openid['openid']) print("%s not unique?" % email["email"])
for openid in items["openids"]:
try:
ConfirmedOpenId.objects.get_or_create(
openid=openid["openid"],
user=user,
photo=saved_photos.get(openid["photo_id"]),
) # pylint: disable=no-member
UserOpenID.objects.get_or_create(
user_id=user.id,
claimed_id=openid["openid"],
display_id=openid["openid"],
)
except django.db.utils.IntegrityError:
print("%s not unique?" % openid["openid"])
fh.close() fh.close()

View File

@@ -1,4 +1,5 @@
''' # -*- coding: utf-8 -*-
"""
Module init Module init
''' """
app_label = __name__ # pylint: disable=invalid-name app_label = __name__ # pylint: disable=invalid-name

View File

@@ -1,6 +1,7 @@
''' # -*- coding: utf-8 -*-
"""
Register models in admin Register models in admin
''' """
from django.contrib import admin from django.contrib import admin
from .models import Photo, ConfirmedEmail, UnconfirmedEmail from .models import Photo, ConfirmedEmail, UnconfirmedEmail

View File

@@ -1,10 +1,11 @@
''' # -*- coding: utf-8 -*-
"""
Classes for our ivatar.ivataraccount.forms Classes for our ivatar.ivataraccount.forms
''' """
from urllib.parse import urlsplit, urlunsplit from urllib.parse import urlsplit, urlunsplit
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from ipware import get_client_ip from ipware import get_client_ip
@@ -20,93 +21,97 @@ MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5
class AddEmailForm(forms.Form): class AddEmailForm(forms.Form):
''' """
Form to handle adding email addresses Form to handle adding email addresses
''' """
email = forms.EmailField( email = forms.EmailField(
label=_('Email'), label=_("Email"),
min_length=MIN_LENGTH_EMAIL, min_length=MIN_LENGTH_EMAIL,
max_length=MAX_LENGTH_EMAIL, max_length=MAX_LENGTH_EMAIL,
) )
def clean_email(self): def clean_email(self):
''' """
Enforce lowercase email Enforce lowercase email
''' """
# TODO: Domain restriction as in libravatar? # TODO: Domain restriction as in libravatar?
return self.cleaned_data['email'].lower() return self.cleaned_data["email"].lower()
def save(self, request): def save(self, request):
''' """
Save the model, ensuring some safety Save the model, ensuring some safety
''' """
user = request.user user = request.user
# Enforce the maximum number of unconfirmed emails a user can have # Enforce the maximum number of unconfirmed emails a user can have
num_unconfirmed = user.unconfirmedemail_set.count() num_unconfirmed = user.unconfirmedemail_set.count()
max_num_unconfirmed_emails = getattr( max_num_unconfirmed_emails = getattr(
settings, settings, "MAX_NUM_UNCONFIRMED_EMAILS", MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT
'MAX_NUM_UNCONFIRMED_EMAILS', )
MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT)
if num_unconfirmed >= max_num_unconfirmed_emails: if num_unconfirmed >= max_num_unconfirmed_emails:
self.add_error(None, _('Too many unconfirmed mail addresses!')) self.add_error(None, _("Too many unconfirmed mail addresses!"))
return False return False
# Check whether or not a confirmation email has been # Check whether or not a confirmation email has been
# sent by this user already # sent by this user already
if UnconfirmedEmail.objects.filter( # pylint: disable=no-member if UnconfirmedEmail.objects.filter( # pylint: disable=no-member
user=user, email=self.cleaned_data['email']).exists(): user=user, email=self.cleaned_data["email"]
self.add_error( ).exists():
'email', self.add_error("email", _("Address already added, currently unconfirmed"))
_('Address already added, currently unconfirmed'))
return False return False
# Check whether or not the email is already confirmed (by someone) # Check whether or not the email is already confirmed (by someone)
check_mail = ConfirmedEmail.objects.filter( check_mail = ConfirmedEmail.objects.filter(email=self.cleaned_data["email"])
email=self.cleaned_data['email'])
if check_mail.exists(): if check_mail.exists():
msg = _('Address already confirmed (by someone else)') msg = _("Address already confirmed (by someone else)")
if check_mail.first().user == request.user: if check_mail.first().user == request.user:
msg = _('Address already confirmed (by you)') msg = _("Address already confirmed (by you)")
self.add_error('email', msg) self.add_error("email", msg)
return False return False
unconfirmed = UnconfirmedEmail() unconfirmed = UnconfirmedEmail()
unconfirmed.email = self.cleaned_data['email'] unconfirmed.email = self.cleaned_data["email"]
unconfirmed.user = user unconfirmed.user = user
unconfirmed.save() unconfirmed.save()
unconfirmed.send_confirmation_mail(url=request.build_absolute_uri('/')[:-1]) unconfirmed.send_confirmation_mail(url=request.build_absolute_uri("/")[:-1])
return True return True
class UploadPhotoForm(forms.Form): class UploadPhotoForm(forms.Form):
''' """
Form handling photo upload Form handling photo upload
''' """
photo = forms.FileField( photo = forms.FileField(
label=_('Photo'), label=_("Photo"),
error_messages={'required': _('You must choose an image to upload.')}) error_messages={"required": _("You must choose an image to upload.")},
)
not_porn = forms.BooleanField( not_porn = forms.BooleanField(
label=_('suitable for all ages (i.e. no offensive content)'), label=_("suitable for all ages (i.e. no offensive content)"),
required=True, required=True,
error_messages={ error_messages={
'required': "required": _(
_('We only host "G-rated" images and so this field must be checked.') 'We only host "G-rated" images and so this field must be checked.'
}) )
},
)
can_distribute = forms.BooleanField( can_distribute = forms.BooleanField(
label=_('can be freely copied'), label=_("can be freely copied"),
required=True, required=True,
error_messages={ error_messages={
'required': "required": _(
_('This field must be checked since we need to be able to distribute photos to third parties.') "This field must be checked since we need to be able to distribute photos to third parties."
}) )
},
)
@staticmethod @staticmethod
def save(request, data): def save(request, data):
''' """
Save the model and assign it to the current user Save the model and assign it to the current user
''' """
# Link this file to the user's profile # Link this file to the user's profile
photo = Photo() photo = Photo()
photo.user = request.user photo.user = request.user
@@ -119,47 +124,48 @@ class UploadPhotoForm(forms.Form):
class AddOpenIDForm(forms.Form): class AddOpenIDForm(forms.Form):
''' """
Form to handle adding OpenID Form to handle adding OpenID
''' """
openid = forms.URLField( openid = forms.URLField(
label=_('OpenID'), label=_("OpenID"),
min_length=MIN_LENGTH_URL, min_length=MIN_LENGTH_URL,
max_length=MAX_LENGTH_URL, max_length=MAX_LENGTH_URL,
initial='http://' initial="http://",
) )
def clean_openid(self): def clean_openid(self):
''' """
Enforce restrictions Enforce restrictions
''' """
# Lowercase hostname port of the URL # Lowercase hostname port of the URL
url = urlsplit(self.cleaned_data['openid']) url = urlsplit(self.cleaned_data["openid"])
data = urlunsplit( data = urlunsplit(
(url.scheme.lower(), url.netloc.lower(), url.path, (url.scheme.lower(), url.netloc.lower(), url.path, url.query, url.fragment)
url.query, url.fragment)) )
# TODO: Domain restriction as in libravatar? # TODO: Domain restriction as in libravatar?
return data return data
def save(self, user): def save(self, user):
''' """
Save the model, ensuring some safety Save the model, ensuring some safety
''' """
if ConfirmedOpenId.objects.filter( # pylint: disable=no-member if ConfirmedOpenId.objects.filter( # pylint: disable=no-member
openid=self.cleaned_data['openid']).exists(): openid=self.cleaned_data["openid"]
self.add_error('openid', _('OpenID already added and confirmed!')) ).exists():
self.add_error("openid", _("OpenID already added and confirmed!"))
return False return False
if UnconfirmedOpenId.objects.filter( # pylint: disable=no-member if UnconfirmedOpenId.objects.filter( # pylint: disable=no-member
openid=self.cleaned_data['openid']).exists(): openid=self.cleaned_data["openid"]
self.add_error( ).exists():
'openid', self.add_error("openid", _("OpenID already added, but not confirmed yet!"))
_('OpenID already added, but not confirmed yet!'))
return False return False
unconfirmed = UnconfirmedOpenId() unconfirmed = UnconfirmedOpenId()
unconfirmed.openid = self.cleaned_data['openid'] unconfirmed.openid = self.cleaned_data["openid"]
unconfirmed.user = user unconfirmed.user = user
unconfirmed.save() unconfirmed.save()
@@ -167,40 +173,50 @@ class AddOpenIDForm(forms.Form):
class UpdatePreferenceForm(forms.ModelForm): class UpdatePreferenceForm(forms.ModelForm):
''' """
Form for updating user preferences Form for updating user preferences
''' """
class Meta: # pylint: disable=too-few-public-methods class Meta: # pylint: disable=too-few-public-methods
''' """
Meta class for UpdatePreferenceForm Meta class for UpdatePreferenceForm
''' """
model = UserPreference model = UserPreference
fields = ['theme'] fields = ["theme"]
class UploadLibravatarExportForm(forms.Form): class UploadLibravatarExportForm(forms.Form):
''' """
Form handling libravatar user export upload Form handling libravatar user export upload
''' """
export_file = forms.FileField( export_file = forms.FileField(
label=_('Export file'), label=_("Export file"),
error_messages={'required': _('You must choose an export file to upload.')}) error_messages={"required": _("You must choose an export file to upload.")},
)
not_porn = forms.BooleanField( not_porn = forms.BooleanField(
label=_('suitable for all ages (i.e. no offensive content)'), label=_("suitable for all ages (i.e. no offensive content)"),
required=True, required=True,
error_messages={ error_messages={
'required': "required": _(
_('We only host "G-rated" images and so this field must be checked.') 'We only host "G-rated" images and so this field must be checked.'
}) )
},
)
can_distribute = forms.BooleanField( can_distribute = forms.BooleanField(
label=_('can be freely copied'), label=_("can be freely copied"),
required=True, required=True,
error_messages={ error_messages={
'required': "required": _(
_('This field must be checked since we need to be able to\ "This field must be checked since we need to be able to\
distribute photos to third parties.') distribute photos to third parties."
}) )
},
)
class DeleteAccountForm(forms.Form): class DeleteAccountForm(forms.Form):
password = forms.CharField(label=_('Password'), required=False, widget=forms.PasswordInput()) password = forms.CharField(
label=_("Password"), required=False, widget=forms.PasswordInput()
)

View File

@@ -1,6 +1,7 @@
''' # -*- coding: utf-8 -*-
"""
Helper method to fetch Gravatar image Helper method to fetch Gravatar image
''' """
from ssl import SSLError from ssl import SSLError
from urllib.request import urlopen, HTTPError, URLError from urllib.request import urlopen, HTTPError, URLError
import hashlib import hashlib
@@ -11,43 +12,47 @@ URL_TIMEOUT = 5 # in seconds
def get_photo(email): def get_photo(email):
''' """
Fetch photo from Gravatar, given an email address Fetch photo from Gravatar, given an email address
''' """
hash_object = hashlib.new('md5') hash_object = hashlib.new("md5")
hash_object.update(email.lower().encode('utf-8')) hash_object.update(email.lower().encode("utf-8"))
thumbnail_url = 'https://secure.gravatar.com/avatar/' + \ thumbnail_url = (
hash_object.hexdigest() + '?s=%i&d=404' % AVATAR_MAX_SIZE "https://secure.gravatar.com/avatar/"
image_url = 'https://secure.gravatar.com/avatar/' + hash_object.hexdigest( + hash_object.hexdigest()
) + '?s=512&d=404' + "?s=%i&d=404" % AVATAR_MAX_SIZE
)
image_url = (
"https://secure.gravatar.com/avatar/" + hash_object.hexdigest() + "?s=512&d=404"
)
# Will redirect to the public profile URL if it exists # Will redirect to the public profile URL if it exists
service_url = 'http://www.gravatar.com/' + hash_object.hexdigest() service_url = "http://www.gravatar.com/" + hash_object.hexdigest()
try: try:
urlopen(image_url, timeout=URL_TIMEOUT) urlopen(image_url, timeout=URL_TIMEOUT)
except HTTPError as exc: except HTTPError as exc:
if exc.code != 404 and exc.code != 503: if exc.code != 404 and exc.code != 503:
print( # pragma: no cover print( # pragma: no cover
'Gravatar fetch failed with an unexpected %s HTTP error' % "Gravatar fetch failed with an unexpected %s HTTP error" % exc.code
exc.code) )
return False return False
except URLError as exc: # pragma: no cover except URLError as exc: # pragma: no cover
print( print(
'Gravatar fetch failed with URL error: %s' % "Gravatar fetch failed with URL error: %s" % exc.reason
exc.reason) # pragma: no cover ) # pragma: no cover
return False # pragma: no cover return False # pragma: no cover
except SSLError as exc: # pragma: no cover except SSLError as exc: # pragma: no cover
print( print(
'Gravatar fetch failed with SSL error: %s' % "Gravatar fetch failed with SSL error: %s" % exc.reason
exc.reason) # pragma: no cover ) # pragma: no cover
return False # pragma: no cover return False # pragma: no cover
return { return {
'thumbnail_url': thumbnail_url, "thumbnail_url": thumbnail_url,
'image_url': image_url, "image_url": image_url,
'width': AVATAR_MAX_SIZE, "width": AVATAR_MAX_SIZE,
'height': AVATAR_MAX_SIZE, "height": AVATAR_MAX_SIZE,
'service_url': service_url, "service_url": service_url,
'service_name': 'Gravatar' "service_name": "Gravatar",
} }

View File

@@ -19,7 +19,7 @@ from django.db import models
from django.utils import timezone from django.utils import timezone
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse_lazy, reverse from django.urls import reverse_lazy, reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core.mail import send_mail from django.core.mail import send_mail
from django.template.loader import render_to_string from django.template.loader import render_to_string

View File

@@ -27,7 +27,7 @@ from django.contrib.auth.views import LoginView
from django.contrib.auth.views import ( from django.contrib.auth.views import (
PasswordResetView as PasswordResetViewOriginal, PasswordResetView as PasswordResetViewOriginal,
) )
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.http import HttpResponseRedirect, HttpResponse from django.http import HttpResponseRedirect, HttpResponse
from django.urls import reverse_lazy, reverse from django.urls import reverse_lazy, reverse
from django.shortcuts import render from django.shortcuts import render

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
""" """
Django settings for ivatar project. Django settings for ivatar project.
""" """
@@ -6,7 +7,7 @@ import os
import logging import logging
log_level = logging.DEBUG # pylint: disable=invalid-name log_level = logging.DEBUG # pylint: disable=invalid-name
logger = logging.getLogger('ivatar') # pylint: disable=invalid-name logger = logging.getLogger("ivatar") # pylint: disable=invalid-name
logger.setLevel(log_level) logger.setLevel(log_level)
PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__))
@@ -14,7 +15,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '=v(+-^t#ahv^a&&e)uf36g8algj$d1@6ou^w(r0@%)#8mlc*zk' SECRET_KEY = "=v(+-^t#ahv^a&&e)uf36g8algj$d1@6ou^w(r0@%)#8mlc*zk"
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
@@ -25,52 +26,52 @@ ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]
ROOT_URLCONF = 'ivatar.urls' ROOT_URLCONF = "ivatar.urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [], "DIRS": [],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ],
}, },
}, },
] ]
WSGI_APPLICATION = 'ivatar.wsgi.application' WSGI_APPLICATION = "ivatar.wsgi.application"
# Database # Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases # https://docs.djangoproject.com/en/2.0/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
} }
} }
@@ -80,16 +81,16 @@ DATABASES = {
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', # noqa "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", # noqa
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', # noqa "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", # noqa
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', # noqa "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", # noqa
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', # noqa "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", # noqa
}, },
] ]
@@ -97,9 +98,9 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/ # https://docs.djangoproject.com/en/2.0/topics/i18n/
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = "en-us"
TIME_ZONE = 'UTC' TIME_ZONE = "UTC"
USE_I18N = True USE_I18N = True
@@ -109,15 +110,10 @@ USE_TZ = True
# Static files configuration (esp. req. during dev.) # Static files configuration (esp. req. during dev.)
PROJECT_ROOT = os.path.abspath( PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
os.path.join( STATIC_URL = "/static/"
os.path.dirname(__file__), STATIC_ROOT = os.path.join(BASE_DIR, "static")
os.pardir
)
)
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
from config import * # pylint: disable=wildcard-import,wrong-import-position,unused-wildcard-import from config import * # pylint: disable=wildcard-import,wrong-import-position,unused-wildcard-import

View File

@@ -1,8 +1,9 @@
''' # -*- coding: utf-8 -*-
"""
Test various other parts of ivatar/libravatar in order Test various other parts of ivatar/libravatar in order
to increase the overall test coverage. Test in here, didn't to increase the overall test coverage. Test in here, didn't
fit anywhere else. fit anywhere else.
''' """
from django.test import TestCase from django.test import TestCase
from django.contrib.auth.models import User from django.contrib.auth.models import User
@@ -12,35 +13,35 @@ from ivatar.ivataraccount.models import pil_format, UserPreference
class Tester(TestCase): class Tester(TestCase):
''' """
Main test class Main test class
''' """
user = None user = None
username = random_string() username = random_string()
def setUp(self): def setUp(self):
''' """
Prepare tests. Prepare tests.
- Create user - Create user
''' """
self.user = User.objects.create_user( self.user = User.objects.create_user(
username=self.username, username=self.username,
) )
def test_pil_format(self): def test_pil_format(self):
''' """
Test pil format function Test pil format function
''' """
self.assertEqual(pil_format('jpg'), 'JPEG') self.assertEqual(pil_format("jpg"), "JPEG")
self.assertEqual(pil_format('jpeg'), 'JPEG') self.assertEqual(pil_format("jpeg"), "JPEG")
self.assertEqual(pil_format('png'), 'PNG') self.assertEqual(pil_format("png"), "PNG")
self.assertEqual(pil_format('gif'), 'GIF') self.assertEqual(pil_format("gif"), "GIF")
self.assertEqual(pil_format('abc'), None) self.assertEqual(pil_format("abc"), None)
def test_userprefs_str(self): def test_userprefs_str(self):
''' """
Test if str representation of UserPreferences is as expected Test if str representation of UserPreferences is as expected
''' """
up = UserPreference(theme='default', user=self.user) up = UserPreference(theme="default", user=self.user)
print(up) print(up)

View File

@@ -1,6 +1,7 @@
''' # -*- coding: utf-8 -*-
"""
Test our views in ivatar.ivataraccount.views and ivatar.views Test our views in ivatar.ivataraccount.views and ivatar.views
''' """
# pylint: disable=too-many-lines # pylint: disable=too-many-lines
import os import os
import django import django
@@ -11,33 +12,34 @@ from django.contrib.auth.models import User
from ivatar.utils import random_string from ivatar.utils import random_string
os.environ['DJANGO_SETTINGS_MODULE'] = 'ivatar.settings' os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
django.setup() django.setup()
class Tester(TestCase): # pylint: disable=too-many-public-methods class Tester(TestCase): # pylint: disable=too-many-public-methods
''' """
Main test class Main test class
''' """
client = Client() client = Client()
user = None user = None
username = random_string() username = random_string()
password = random_string() password = random_string()
email = '%s@%s.%s' % (username, random_string(), random_string(2)) email = "%s@%s.%s" % (username, random_string(), random_string(2))
# Dunno why random tld doesn't work, but I'm too lazy now to investigate # Dunno why random tld doesn't work, but I'm too lazy now to investigate
openid = 'http://%s.%s.%s/' % (username, random_string(), 'org') openid = "http://%s.%s.%s/" % (username, random_string(), "org")
def login(self): def login(self):
''' """
Login as user Login as user
''' """
self.client.login(username=self.username, password=self.password) self.client.login(username=self.username, password=self.password)
def setUp(self): def setUp(self):
''' """
Prepare for tests. Prepare for tests.
- Create user - Create user
''' """
self.user = User.objects.create_user( self.user = User.objects.create_user(
username=self.username, username=self.username,
password=self.password, password=self.password,
@@ -47,19 +49,19 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
""" """
Test contact page Test contact page
""" """
response = self.client.get(reverse('contact')) response = self.client.get(reverse("contact"))
self.assertEqual(response.status_code, 200, 'no 200 ok?') self.assertEqual(response.status_code, 200, "no 200 ok?")
def test_description_page(self): def test_description_page(self):
""" """
Test description page Test description page
""" """
response = self.client.get(reverse('description')) response = self.client.get(reverse("description"))
self.assertEqual(response.status_code, 200, 'no 200 ok?') self.assertEqual(response.status_code, 200, "no 200 ok?")
def test_security_page(self): def test_security_page(self):
""" """
Test security page Test security page
""" """
response = self.client.get(reverse('security')) response = self.client.get(reverse("security"))
self.assertEqual(response.status_code, 200, 'no 200 ok?') self.assertEqual(response.status_code, 200, "no 200 ok?")

View File

@@ -1,6 +1,7 @@
''' # -*- coding: utf-8 -*-
"""
Test our utils from ivatar.utils Test our utils from ivatar.utils
''' """
from django.test import TestCase from django.test import TestCase
@@ -8,18 +9,18 @@ from ivatar.utils import openid_variations
class Tester(TestCase): class Tester(TestCase):
''' """
Main test class Main test class
''' """
def test_openid_variations(self): def test_openid_variations(self):
''' """
Test if the OpenID variation "generator" does the correct thing Test if the OpenID variation "generator" does the correct thing
''' """
openid0 = 'http://user.url/' openid0 = "http://user.url/"
openid1 = 'http://user.url' openid1 = "http://user.url"
openid2 = 'https://user.url/' openid2 = "https://user.url/"
openid3 = 'https://user.url' openid3 = "https://user.url"
# First variation # First variation
self.assertEqual(openid_variations(openid0)[0], openid0) self.assertEqual(openid_variations(openid0)[0], openid0)

View File

@@ -1,6 +1,7 @@
''' # -*- coding: utf-8 -*-
"""
Test our views in ivatar.ivataraccount.views and ivatar.views Test our views in ivatar.ivataraccount.views and ivatar.views
''' """
# pylint: disable=too-many-lines # pylint: disable=too-many-lines
import os import os
import django import django
@@ -10,33 +11,34 @@ from django.contrib.auth.models import User
from ivatar.utils import random_string from ivatar.utils import random_string
os.environ['DJANGO_SETTINGS_MODULE'] = 'ivatar.settings' os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
django.setup() django.setup()
class Tester(TestCase): # pylint: disable=too-many-public-methods class Tester(TestCase): # pylint: disable=too-many-public-methods
''' """
Main test class Main test class
''' """
client = Client() client = Client()
user = None user = None
username = random_string() username = random_string()
password = random_string() password = random_string()
email = '%s@%s.%s' % (username, random_string(), random_string(2)) email = "%s@%s.%s" % (username, random_string(), random_string(2))
# Dunno why random tld doesn't work, but I'm too lazy now to investigate # Dunno why random tld doesn't work, but I'm too lazy now to investigate
openid = 'http://%s.%s.%s/' % (username, random_string(), 'org') openid = "http://%s.%s.%s/" % (username, random_string(), "org")
def login(self): def login(self):
''' """
Login as user Login as user
''' """
self.client.login(username=self.username, password=self.password) self.client.login(username=self.username, password=self.password)
def setUp(self): def setUp(self):
''' """
Prepare for tests. Prepare for tests.
- Create user - Create user
''' """
self.user = User.objects.create_user( self.user = User.objects.create_user(
username=self.username, username=self.username,
password=self.password, password=self.password,
@@ -46,8 +48,9 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
""" """
Test incorrect digest Test incorrect digest
""" """
response = self.client.get('/avatar/%s' % 'x'*65, follow=True) response = self.client.get("/avatar/%s" % "x" * 65, follow=True)
self.assertRedirects( self.assertRedirects(
response=response, response=response,
expected_url='/static/img/deadbeef.png', expected_url="/static/img/deadbeef.png",
msg_prefix='Why does an invalid hash not redirect to deadbeef?') msg_prefix="Why does an invalid hash not redirect to deadbeef?",
)

View File

@@ -1,22 +1,27 @@
''' # -*- coding: utf-8 -*-
"""
Unit tests for WSGI Unit tests for WSGI
''' """
import unittest import unittest
import os import os
import django import django
os.environ['DJANGO_SETTINGS_MODULE'] = 'ivatar.settings'
os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
django.setup() django.setup()
class TestCase(unittest.TestCase): class TestCase(unittest.TestCase):
''' """
Simple testcase to see if WSGI loads correctly Simple testcase to see if WSGI loads correctly
''' """
def test_run_wsgi(self): def test_run_wsgi(self):
''' """
Run wsgi import Run wsgi import
''' """
import ivatar.wsgi # pylint: disable=import-outside-toplevel import ivatar.wsgi # pylint: disable=import-outside-toplevel
self.assertEqual(ivatar.wsgi.application.__class__,
django.core.handlers.wsgi.WSGIHandler) self.assertEqual(
ivatar.wsgi.application.__class__, django.core.handlers.wsgi.WSGIHandler
)

View File

@@ -1,8 +1,9 @@
''' # -*- coding: utf-8 -*-
"""
Classes for our ivatar.tools.forms Classes for our ivatar.tools.forms
''' """
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.forms.utils import ErrorList from django.forms.utils import ErrorList
@@ -12,45 +13,40 @@ from ivatar.settings import MIN_LENGTH_EMAIL, MAX_LENGTH_EMAIL
class CheckDomainForm(forms.Form): class CheckDomainForm(forms.Form):
''' """
Form handling domain check Form handling domain check
''' """
domain = forms.CharField( domain = forms.CharField(
label=_('Domain'), label=_("Domain"),
required=True, required=True,
error_messages={ error_messages={"required": _("Cannot check without a domain name.")},
'required':
_('Cannot check without a domain name.')
}
) )
class CheckForm(forms.Form): class CheckForm(forms.Form):
''' """
Form handling check Form handling check
''' """
mail = forms.EmailField( mail = forms.EmailField(
label=_('E-Mail'), label=_("E-Mail"),
required=False, required=False,
min_length=MIN_LENGTH_EMAIL, min_length=MIN_LENGTH_EMAIL,
max_length=MAX_LENGTH_EMAIL, max_length=MAX_LENGTH_EMAIL,
error_messages={ error_messages={"required": _("Cannot check without a domain name.")},
'required': )
_('Cannot check without a domain name.')
})
openid = forms.CharField( openid = forms.CharField(
label=_('OpenID'), label=_("OpenID"),
required=False, required=False,
min_length=MIN_LENGTH_URL, min_length=MIN_LENGTH_URL,
max_length=MAX_LENGTH_URL, max_length=MAX_LENGTH_URL,
error_messages={ error_messages={"required": _("Cannot check without an openid name.")},
'required': )
_('Cannot check without an openid name.')
})
size = forms.IntegerField( size = forms.IntegerField(
label=_('Size'), label=_("Size"),
initial=80, initial=80,
min_value=5, min_value=5,
max_value=AVATAR_MAX_SIZE, max_value=AVATAR_MAX_SIZE,
@@ -58,24 +54,24 @@ class CheckForm(forms.Form):
) )
default_opt = forms.ChoiceField( default_opt = forms.ChoiceField(
label=_('Default'), label=_("Default"),
required=False, required=False,
widget=forms.RadioSelect, widget=forms.RadioSelect,
choices=[ choices=[
('retro', _('Retro style (similar to GitHub)')), ("retro", _("Retro style (similar to GitHub)")),
('robohash', _('Roboter style')), ("robohash", _("Roboter style")),
('pagan', _('Retro adventure character')), ("pagan", _("Retro adventure character")),
('wavatar', _('Wavatar style')), ("wavatar", _("Wavatar style")),
('monsterid', _('Monster style')), ("monsterid", _("Monster style")),
('identicon', _('Identicon style')), ("identicon", _("Identicon style")),
('mm', _('Mystery man')), ("mm", _("Mystery man")),
('mmng', _('Mystery man NextGen')), ("mmng", _("Mystery man NextGen")),
('none', _('None')), ("none", _("None")),
], ],
) )
default_url = forms.URLField( default_url = forms.URLField(
label=_('Default URL'), label=_("Default URL"),
min_length=1, min_length=1,
max_length=MAX_LENGTH_URL, max_length=MAX_LENGTH_URL,
required=False, required=False,
@@ -83,28 +79,27 @@ class CheckForm(forms.Form):
def clean(self): def clean(self):
self.cleaned_data = super().clean() self.cleaned_data = super().clean()
mail = self.cleaned_data.get('mail') mail = self.cleaned_data.get("mail")
openid = self.cleaned_data.get('openid') openid = self.cleaned_data.get("openid")
default_url = self.cleaned_data.get('default_url') default_url = self.cleaned_data.get("default_url")
default_opt = self.cleaned_data.get('default_opt') default_opt = self.cleaned_data.get("default_opt")
if default_url and default_opt and default_opt != 'none': if default_url and default_opt and default_opt != "none":
if not 'default_url' in self._errors: if "default_url" not in self._errors:
self._errors['default_url'] = ErrorList() self._errors["default_url"] = ErrorList()
if not 'default_opt' in self._errors: if "default_opt" not in self._errors:
self._errors['default_opt'] = ErrorList() self._errors["default_opt"] = ErrorList()
errstring = _('Only default URL OR default keyword may be specified') errstring = _("Only default URL OR default keyword may be specified")
self._errors['default_url'].append(errstring) self._errors["default_url"].append(errstring)
self._errors['default_opt'].append(errstring) self._errors["default_opt"].append(errstring)
if not mail and not openid: if not mail and not openid:
raise ValidationError(_('Either OpenID or mail must be specified')) raise ValidationError(_("Either OpenID or mail must be specified"))
return self.cleaned_data return self.cleaned_data
def clean_openid(self): def clean_openid(self):
data = self.cleaned_data['openid'] data = self.cleaned_data["openid"]
return data.lower() return data.lower()
def clean_mail(self): def clean_mail(self):
data = self.cleaned_data['mail'] data = self.cleaned_data["mail"]
print(data)
return data.lower() return data.lower()

View File

@@ -1,57 +1,48 @@
''' # -*- coding: utf-8 -*-
"""
Test our views in ivatar.ivataraccount.views and ivatar.views Test our views in ivatar.ivataraccount.views and ivatar.views
''' """
# pylint: disable=too-many-lines # pylint: disable=too-many-lines
from urllib.parse import urlsplit
from io import BytesIO
import io
import os import os
import django import django
from django.test import TestCase from django.test import TestCase
from django.test import Client from django.test import Client
from django.urls import reverse from django.urls import reverse
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth import authenticate
import hashlib
from libravatar import libravatar_url os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
from PIL import Image
os.environ['DJANGO_SETTINGS_MODULE'] = 'ivatar.settings'
django.setup() django.setup()
# pylint: disable=wrong-import-position # pylint: disable=wrong-import-position
from ivatar import settings
from ivatar.ivataraccount.forms import MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT
from ivatar.ivataraccount.models import Photo, ConfirmedOpenId
from ivatar.utils import random_string from ivatar.utils import random_string
# pylint: enable=wrong-import-position # pylint: enable=wrong-import-position
class Tester(TestCase): # pylint: disable=too-many-public-methods class Tester(TestCase): # pylint: disable=too-many-public-methods
''' """
Main test class Main test class
''' """
client = Client() client = Client()
user = None user = None
username = random_string() username = random_string()
password = random_string() password = random_string()
email = '%s@%s.%s' % (username, random_string(), random_string(2)) email = "%s@%s.%s" % (username, random_string(), random_string(2))
# Dunno why random tld doesn't work, but I'm too lazy now to investigate # Dunno why random tld doesn't work, but I'm too lazy now to investigate
openid = 'http://%s.%s.%s/' % (username, random_string(), 'org') openid = "http://%s.%s.%s/" % (username, random_string(), "org")
def login(self): def login(self):
''' """
Login as user Login as user
''' """
self.client.login(username=self.username, password=self.password) self.client.login(username=self.username, password=self.password)
def setUp(self): def setUp(self):
''' """
Prepare for tests. Prepare for tests.
- Create user - Create user
''' """
self.user = User.objects.create_user( self.user = User.objects.create_user(
username=self.username, username=self.username,
password=self.password, password=self.password,
@@ -61,12 +52,12 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
""" """
Test check page Test check page
""" """
response = self.client.get(reverse('tools_check')) response = self.client.get(reverse("tools_check"))
self.assertEqual(response.status_code, 200, 'no 200 ok?') self.assertEqual(response.status_code, 200, "no 200 ok?")
def test_check_domain(self): def test_check_domain(self):
""" """
Test check domain page Test check domain page
""" """
response = self.client.get(reverse('tools_check_domain')) response = self.client.get(reverse("tools_check_domain"))
self.assertEqual(response.status_code, 200, 'no 200 ok?') self.assertEqual(response.status_code, 200, "no 200 ok?")

View File

@@ -1,12 +1,13 @@
''' # -*- coding: utf-8 -*-
"""
ivatar/tools URL configuration ivatar/tools URL configuration
''' """
from django.conf.urls import url from django.conf.urls import url
from .views import CheckView, CheckDomainView from .views import CheckView, CheckDomainView
urlpatterns = [ # pylint: disable=invalid-name urlpatterns = [ # pylint: disable=invalid-name
url('check/', CheckView.as_view(), name='tools_check'), url("check/", CheckView.as_view(), name="tools_check"),
url('check_domain/', CheckDomainView.as_view(), name='tools_check_domain'), url("check_domain/", CheckDomainView.as_view(), name="tools_check_domain"),
url('check_domain$', CheckDomainView.as_view(), name='tools_check_domain'), url("check_domain$", CheckDomainView.as_view(), name="tools_check_domain"),
] ]

View File

@@ -1,6 +1,7 @@
''' # -*- coding: utf-8 -*-
"""
View classes for ivatar/tools/ View classes for ivatar/tools/
''' """
from socket import inet_ntop, AF_INET6 from socket import inet_ntop, AF_INET6
import hashlib import hashlib
import random import random
@@ -16,49 +17,59 @@ from libravatar import SECURE_BASE_URL as LIBRAVATAR_SECURE_BASE_URL
from libravatar import BASE_URL as LIBRAVATAR_BASE_URL from libravatar import BASE_URL as LIBRAVATAR_BASE_URL
from ivatar.settings import SECURE_BASE_URL, BASE_URL from ivatar.settings import SECURE_BASE_URL, BASE_URL
from .forms import CheckDomainForm, CheckForm # pylint: disable=relative-beyond-top-level from .forms import (
CheckDomainForm,
CheckForm,
) # pylint: disable=relative-beyond-top-level
class CheckDomainView(FormView): class CheckDomainView(FormView):
''' """
View class for checking a domain View class for checking a domain
''' """
template_name = 'check_domain.html'
template_name = "check_domain.html"
form_class = CheckDomainForm form_class = CheckDomainForm
success_url = reverse('tools_check_domain') success_url = reverse("tools_check_domain")
def form_valid(self, form): def form_valid(self, form):
result = {} result = {}
super().form_valid(form) super().form_valid(form)
domain = form.cleaned_data['domain'] domain = form.cleaned_data["domain"]
result['avatar_server_http'] = lookup_avatar_server(domain, False) result["avatar_server_http"] = lookup_avatar_server(domain, False)
if result['avatar_server_http']: if result["avatar_server_http"]:
result['avatar_server_http_ipv4'] = lookup_ip_address( result["avatar_server_http_ipv4"] = lookup_ip_address(
result['avatar_server_http'], result["avatar_server_http"], False
False) )
result['avatar_server_http_ipv6'] = lookup_ip_address( result["avatar_server_http_ipv6"] = lookup_ip_address(
result['avatar_server_http'], result["avatar_server_http"], True
True) )
result['avatar_server_https'] = lookup_avatar_server(domain, True) result["avatar_server_https"] = lookup_avatar_server(domain, True)
if result['avatar_server_https']: if result["avatar_server_https"]:
result['avatar_server_https_ipv4'] = lookup_ip_address( result["avatar_server_https_ipv4"] = lookup_ip_address(
result['avatar_server_https'], result["avatar_server_https"], False
False) )
result['avatar_server_https_ipv6'] = lookup_ip_address( result["avatar_server_https_ipv6"] = lookup_ip_address(
result['avatar_server_https'], result["avatar_server_https"], True
True) )
return render(self.request, self.template_name, { return render(
'form': form, self.request,
'result': result, self.template_name,
}) {
"form": form,
"result": result,
},
)
class CheckView(FormView): class CheckView(FormView):
''' """
View class for checking an e-mail or openid address View class for checking an e-mail or openid address
''' """
template_name = 'check.html'
template_name = "check.html"
form_class = CheckForm form_class = CheckForm
success_url = reverse('tools_check') success_url = reverse("tools_check")
def form_valid(self, form): def form_valid(self, form):
mailurl = None mailurl = None
@@ -73,82 +84,88 @@ class CheckView(FormView):
super().form_valid(form) super().form_valid(form)
if form.cleaned_data['default_url']: if form.cleaned_data["default_url"]:
default_url = form.cleaned_data['default_url'] default_url = form.cleaned_data["default_url"]
elif form.cleaned_data['default_opt'] and form.cleaned_data['default_opt'] != 'none': elif (
default_url = form.cleaned_data['default_opt'] form.cleaned_data["default_opt"]
and form.cleaned_data["default_opt"] != "none"
):
default_url = form.cleaned_data["default_opt"]
else: else:
default_url = None default_url = None
if 'size' in form.cleaned_data: if "size" in form.cleaned_data:
size = form.cleaned_data['size'] size = form.cleaned_data["size"]
if form.cleaned_data['mail']: if form.cleaned_data["mail"]:
mailurl = libravatar_url( mailurl = libravatar_url(
email=form.cleaned_data['mail'], email=form.cleaned_data["mail"], size=size, default=default_url
size=size, )
default=default_url)
mailurl = mailurl.replace(LIBRAVATAR_BASE_URL, BASE_URL) mailurl = mailurl.replace(LIBRAVATAR_BASE_URL, BASE_URL)
mailurl_secure = libravatar_url( mailurl_secure = libravatar_url(
email=form.cleaned_data['mail'], email=form.cleaned_data["mail"],
size=size, size=size,
https=True, https=True,
default=default_url) default=default_url,
)
mailurl_secure = mailurl_secure.replace( mailurl_secure = mailurl_secure.replace(
LIBRAVATAR_SECURE_BASE_URL, LIBRAVATAR_SECURE_BASE_URL, SECURE_BASE_URL
SECURE_BASE_URL) )
mail_hash = parse_user_identity( mail_hash = parse_user_identity(
email=form.cleaned_data['mail'], email=form.cleaned_data["mail"], openid=None
openid=None)[0] )[0]
hash_obj = hashlib.new('sha256') hash_obj = hashlib.new("sha256")
hash_obj.update(form.cleaned_data['mail'].encode('utf-8')) hash_obj.update(form.cleaned_data["mail"].encode("utf-8"))
mail_hash256 = hash_obj.hexdigest() mail_hash256 = hash_obj.hexdigest()
mailurl_secure_256 = mailurl_secure.replace( mailurl_secure_256 = mailurl_secure.replace(mail_hash, mail_hash256)
mail_hash, if form.cleaned_data["openid"]:
mail_hash256) if not form.cleaned_data["openid"].startswith(
if form.cleaned_data['openid']: "http://"
if not form.cleaned_data['openid'].startswith('http://') and \ ) and not form.cleaned_data["openid"].startswith("https://"):
not form.cleaned_data['openid'].startswith('https://'): form.cleaned_data["openid"] = "http://%s" % form.cleaned_data["openid"]
form.cleaned_data['openid'] = 'http://%s' % form.cleaned_data['openid']
openidurl = libravatar_url( openidurl = libravatar_url(
openid=form.cleaned_data['openid'], openid=form.cleaned_data["openid"], size=size, default=default_url
size=size, )
default=default_url)
openidurl = openidurl.replace(LIBRAVATAR_BASE_URL, BASE_URL) openidurl = openidurl.replace(LIBRAVATAR_BASE_URL, BASE_URL)
openidurl_secure = libravatar_url( openidurl_secure = libravatar_url(
openid=form.cleaned_data['openid'], openid=form.cleaned_data["openid"],
size=size, size=size,
https=True, https=True,
default=default_url) default=default_url,
)
openidurl_secure = openidurl_secure.replace( openidurl_secure = openidurl_secure.replace(
LIBRAVATAR_SECURE_BASE_URL, LIBRAVATAR_SECURE_BASE_URL, SECURE_BASE_URL
SECURE_BASE_URL) )
openid_hash = parse_user_identity( openid_hash = parse_user_identity(
openid=form.cleaned_data['openid'], openid=form.cleaned_data["openid"], email=None
email=None)[0] )[0]
return render(self.request, self.template_name, { return render(
'form': form, self.request,
'mailurl': mailurl, self.template_name,
'openidurl': openidurl, {
'mailurl_secure': mailurl_secure, "form": form,
'mailurl_secure_256': mailurl_secure_256, "mailurl": mailurl,
'openidurl_secure': openidurl_secure, "openidurl": openidurl,
'mail_hash': mail_hash, "mailurl_secure": mailurl_secure,
'mail_hash256': mail_hash256, "mailurl_secure_256": mailurl_secure_256,
'openid_hash': openid_hash, "openidurl_secure": openidurl_secure,
'size': size, "mail_hash": mail_hash,
}) "mail_hash256": mail_hash256,
"openid_hash": openid_hash,
"size": size,
},
)
def lookup_avatar_server(domain, https): def lookup_avatar_server(domain, https):
''' """
Extract the avatar server from an SRV record in the DNS zone Extract the avatar server from an SRV record in the DNS zone
The SRV records should look like this: The SRV records should look like this:
_avatars._tcp.example.com. IN SRV 0 0 80 avatars.example.com _avatars._tcp.example.com. IN SRV 0 0 80 avatars.example.com
_avatars-sec._tcp.example.com. IN SRV 0 0 443 avatars.example.com _avatars-sec._tcp.example.com. IN SRV 0 0 443 avatars.example.com
''' """
if domain and len(domain) > 60: if domain and len(domain) > 60:
domain = domain[:60] domain = domain[:60]
@@ -161,27 +178,35 @@ def lookup_avatar_server(domain, https):
DNS.DiscoverNameServers() DNS.DiscoverNameServers()
try: try:
dns_request = DNS.Request(name=service_name, qtype='SRV').req() dns_request = DNS.Request(name=service_name, qtype="SRV").req()
except DNS.DNSError as message: except DNS.DNSError as message:
print("DNS Error: %s (%s)" % (message, domain)) print("DNS Error: %s (%s)" % (message, domain))
return None return None
if dns_request.header['status'] == 'NXDOMAIN': if dns_request.header["status"] == "NXDOMAIN":
# Not an error, but no point in going any further # Not an error, but no point in going any further
return None return None
if dns_request.header['status'] != 'NOERROR': if dns_request.header["status"] != "NOERROR":
print("DNS Error: status=%s (%s)" % (dns_request.header['status'], domain)) print("DNS Error: status=%s (%s)" % (dns_request.header["status"], domain))
return None return None
records = [] records = []
for answer in dns_request.answers: for answer in dns_request.answers:
if ('data' not in answer) or (not answer['data']) or \ if (
(not answer['typename']) or (answer['typename'] != 'SRV'): ("data" not in answer)
or (not answer["data"])
or (not answer["typename"])
or (answer["typename"] != "SRV")
):
continue continue
record = {'priority': int(answer['data'][0]), 'weight': int(answer['data'][1]), record = {
'port': int(answer['data'][2]), 'target': answer['data'][3]} "priority": int(answer["data"][0]),
"weight": int(answer["data"][1]),
"port": int(answer["data"][2]),
"target": answer["data"][3],
}
records.append(record) records.append(record)
@@ -194,38 +219,38 @@ def lookup_avatar_server(domain, https):
def srv_hostname(records): def srv_hostname(records):
''' """
Return the right (target, port) pair from a list of SRV records. Return the right (target, port) pair from a list of SRV records.
''' """
if len(records) < 1: if len(records) < 1:
return (None, None) return (None, None)
if len(records) == 1: if len(records) == 1:
ret = records[0] ret = records[0]
return (ret['target'], ret['port']) return (ret["target"], ret["port"])
# Keep only the servers in the top priority # Keep only the servers in the top priority
priority_records = [] priority_records = []
total_weight = 0 total_weight = 0
top_priority = records[0]['priority'] # highest priority = lowest number top_priority = records[0]["priority"] # highest priority = lowest number
for ret in records: for ret in records:
if ret['priority'] > top_priority: if ret["priority"] > top_priority:
# ignore the record (ret has lower priority) # ignore the record (ret has lower priority)
continue continue
# Take care - this if is only a if, if the above if # Take care - this if is only a if, if the above if
# uses continue at the end. else it should be an elsif # uses continue at the end. else it should be an elsif
if ret['priority'] < top_priority: if ret["priority"] < top_priority:
# reset the aretay (ret has higher priority) # reset the aretay (ret has higher priority)
top_priority = ret['priority'] top_priority = ret["priority"]
total_weight = 0 total_weight = 0
priority_records = [] priority_records = []
total_weight += ret['weight'] total_weight += ret["weight"]
if ret['weight'] > 0: if ret["weight"] > 0:
priority_records.append((total_weight, ret)) priority_records.append((total_weight, ret))
else: else:
# zero-weigth elements must come first # zero-weigth elements must come first
@@ -233,7 +258,7 @@ def srv_hostname(records):
if len(priority_records) == 1: if len(priority_records) == 1:
unused, ret = priority_records[0] # pylint: disable=unused-variable unused, ret = priority_records[0] # pylint: disable=unused-variable
return (ret['target'], ret['port']) return (ret["target"], ret["port"])
# Select first record according to RFC2782 weight ordering algorithm (page 3) # Select first record according to RFC2782 weight ordering algorithm (page 3)
random_number = random.randint(0, total_weight) random_number = random.randint(0, total_weight)
@@ -242,9 +267,9 @@ def srv_hostname(records):
weighted_index, ret = record weighted_index, ret = record
if weighted_index >= random_number: if weighted_index >= random_number:
return (ret['target'], ret['port']) return (ret["target"], ret["port"])
print('There is something wrong with our SRV weight ordering algorithm') print("There is something wrong with our SRV weight ordering algorithm")
return (None, None) return (None, None)
@@ -263,19 +288,21 @@ def lookup_ip_address(hostname, ipv6):
print("DNS Error: %s (%s)" % (message, hostname)) print("DNS Error: %s (%s)" % (message, hostname))
return None return None
if dns_request.header['status'] != 'NOERROR': if dns_request.header["status"] != "NOERROR":
print("DNS Error: status=%s (%s)" % (dns_request.header['status'], hostname)) print("DNS Error: status=%s (%s)" % (dns_request.header["status"], hostname))
return None return None
for answer in dns_request.answers: for answer in dns_request.answers:
if ('data' not in answer) or (not answer['data']): if ("data" not in answer) or (not answer["data"]):
continue continue
if (ipv6 and answer['typename'] != 'AAAA') or (not ipv6 and answer['typename'] != 'A'): if (ipv6 and answer["typename"] != "AAAA") or (
not ipv6 and answer["typename"] != "A"
):
continue # skip CNAME records continue # skip CNAME records
if ipv6: if ipv6:
return inet_ntop(AF_INET6, answer['data']) return inet_ntop(AF_INET6, answer["data"])
return answer['data'] return answer["data"]
return None return None

View File

@@ -1,6 +1,7 @@
''' # -*- coding: utf-8 -*-
"""
ivatar URL configuration ivatar URL configuration
''' """
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from django.conf.urls import url from django.conf.urls import url
@@ -10,33 +11,48 @@ from ivatar import settings
from .views import AvatarImageView, GravatarProxyView, StatsView from .views import AvatarImageView, GravatarProxyView, StatsView
urlpatterns = [ # pylint: disable=invalid-name urlpatterns = [ # pylint: disable=invalid-name
path('admin/', admin.site.urls), path("admin/", admin.site.urls),
path('i18n/', include('django.conf.urls.i18n')), path("i18n/", include("django.conf.urls.i18n")),
url('openid/', include('django_openid_auth.urls')), url("openid/", include("django_openid_auth.urls")),
url('tools/', include('ivatar.tools.urls')), url("tools/", include("ivatar.tools.urls")),
url(r"avatar/(?P<digest>\w{64})", AvatarImageView.as_view(), name="avatar_view"),
url(r"avatar/(?P<digest>\w{32})", AvatarImageView.as_view(), name="avatar_view"),
url(r"avatar/$", AvatarImageView.as_view(), name="avatar_view"),
url( url(
r'avatar/(?P<digest>\w{64})', r"avatar/(?P<digest>\w*)",
AvatarImageView.as_view(), name='avatar_view'), RedirectView.as_view(url="/static/img/deadbeef.png"),
name="invalid_hash",
),
url( url(
r'avatar/(?P<digest>\w{32})', r"gravatarproxy/(?P<digest>\w*)",
AvatarImageView.as_view(), name='avatar_view'), GravatarProxyView.as_view(),
url(r'avatar/$', AvatarImageView.as_view(), name='avatar_view'), name="gravatarproxy",
),
url( url(
r'avatar/(?P<digest>\w*)', "description/",
RedirectView.as_view(url='/static/img/deadbeef.png'), name='invalid_hash'), TemplateView.as_view(template_name="description.html"),
url( name="description",
r'gravatarproxy/(?P<digest>\w*)', ),
GravatarProxyView.as_view(), name='gravatarproxy'),
url('description/', TemplateView.as_view(template_name='description.html'), name='description'),
# The following two are TODO TODO TODO TODO TODO # The following two are TODO TODO TODO TODO TODO
url('run_your_own/', url(
TemplateView.as_view(template_name='run_your_own.html'), name='run_your_own'), "run_your_own/",
url('features/', TemplateView.as_view(template_name='features.html'), name='features'), TemplateView.as_view(template_name="run_your_own.html"),
url('security/', TemplateView.as_view(template_name='security.html'), name='security'), name="run_your_own",
url('privacy/', TemplateView.as_view(template_name='privacy.html'), name='privacy'), ),
url('contact/', TemplateView.as_view(template_name='contact.html'), name='contact'), url(
path('talk_to_us/', RedirectView.as_view(url='/contact'), name='talk_to_us'), "features/",
url('stats/', StatsView.as_view(), name='stats'), TemplateView.as_view(template_name="features.html"),
name="features",
),
url(
"security/",
TemplateView.as_view(template_name="security.html"),
name="security",
),
url("privacy/", TemplateView.as_view(template_name="privacy.html"), name="privacy"),
url("contact/", TemplateView.as_view(template_name="contact.html"), name="contact"),
path("talk_to_us/", RedirectView.as_view(url="/contact"), name="talk_to_us"),
url("stats/", StatsView.as_view(), name="stats"),
] ]
MAINTENANCE = False MAINTENANCE = False
@@ -47,11 +63,15 @@ except: # pylint: disable=bare-except
pass pass
if MAINTENANCE: if MAINTENANCE:
urlpatterns.append(url('', TemplateView.as_view(template_name='maintenance.html'), name='home')) urlpatterns.append(
urlpatterns.insert(3, url('accounts/', RedirectView.as_view(url='/'))) url("", TemplateView.as_view(template_name="maintenance.html"), name="home")
)
urlpatterns.insert(3, url("accounts/", RedirectView.as_view(url="/")))
else: else:
urlpatterns.append(url('', TemplateView.as_view(template_name='home.html'), name='home')) urlpatterns.append(
urlpatterns.insert(3, url('accounts/', include('ivatar.ivataraccount.urls'))) url("", TemplateView.as_view(template_name="home.html"), name="home")
)
urlpatterns.insert(3, url("accounts/", include("ivatar.ivataraccount.urls")))
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@@ -1,50 +1,56 @@
''' # -*- coding: utf-8 -*-
"""
Simple module providing reusable random_string function Simple module providing reusable random_string function
''' """
import random import random
import string import string
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
def random_string(length=10): def random_string(length=10):
''' """
Return some random string with default length 10 Return some random string with default length 10
''' """
return ''.join(random.SystemRandom().choice( return "".join(
string.ascii_lowercase + string.digits) for _ in range(length)) random.SystemRandom().choice(string.ascii_lowercase + string.digits)
for _ in range(length)
)
def openid_variations(openid): def openid_variations(openid):
''' """
Return the various OpenID variations, ALWAYS in the same order: Return the various OpenID variations, ALWAYS in the same order:
- http w/ trailing slash - http w/ trailing slash
- http w/o trailing slash - http w/o trailing slash
- https w/ trailing slash - https w/ trailing slash
- https w/o trailing slash - https w/o trailing slash
''' """
# Make the 'base' version: http w/ trailing slash # Make the 'base' version: http w/ trailing slash
if openid.startswith('https://'): if openid.startswith("https://"):
openid = openid.replace('https://', 'http://') openid = openid.replace("https://", "http://")
if openid[-1] != '/': if openid[-1] != "/":
openid = openid + '/' openid = openid + "/"
# http w/o trailing slash # http w/o trailing slash
var1 = openid[0:-1] var1 = openid[0:-1]
var2 = openid.replace('http://', 'https://') var2 = openid.replace("http://", "https://")
var3 = var2[0:-1] var3 = var2[0:-1]
return (openid, var1, var2, var3) return (openid, var1, var2, var3)
def mm_ng(idhash, size=80, add_red=0, add_green=0, add_blue=0): #pylint: disable=too-many-locals
''' def mm_ng(
idhash, size=80, add_red=0, add_green=0, add_blue=0
): # pylint: disable=too-many-locals
"""
Return an MM (mystery man) image, based on a given hash Return an MM (mystery man) image, based on a given hash
add some red, green or blue, if specified add some red, green or blue, if specified
''' """
# Make sure the lightest bg color we paint is e0, else # Make sure the lightest bg color we paint is e0, else
# we do not see the MM any more # we do not see the MM any more
if idhash[0] == 'f': if idhash[0] == "f":
idhash = 'e0' idhash = "e0"
# How large is the circle? # How large is the circle?
circlesize = size * 0.6 circlesize = size * 0.6
@@ -62,44 +68,47 @@ def mm_ng(idhash, size=80, add_red=0, add_green=0, add_blue=0): #pylint: disabl
blue = idhash[0:2] blue = idhash[0:2]
# Add some red (i/a) and make sure it's not over 255 # Add some red (i/a) and make sure it's not over 255
red = hex(int(red, 16)+add_red).replace('0x', '') red = hex(int(red, 16) + add_red).replace("0x", "")
if int(red, 16) > 255: if int(red, 16) > 255:
red = 'ff' red = "ff"
if len(red) == 1: if len(red) == 1:
red = '0%s' % red red = "0%s" % red
# Add some green (i/a) and make sure it's not over 255 # Add some green (i/a) and make sure it's not over 255
green = hex(int(green, 16)+add_green).replace('0x', '') green = hex(int(green, 16) + add_green).replace("0x", "")
if int(green, 16) > 255: if int(green, 16) > 255:
green = 'ff' green = "ff"
if len(green) == 1: if len(green) == 1:
green = '0%s' % green green = "0%s" % green
# Add some blue (i/a) and make sure it's not over 255 # Add some blue (i/a) and make sure it's not over 255
blue = hex(int(blue, 16)+add_blue).replace('0x', '') blue = hex(int(blue, 16) + add_blue).replace("0x", "")
if int(blue, 16) > 255: if int(blue, 16) > 255:
blue = 'ff' blue = "ff"
if len(blue) == 1: if len(blue) == 1:
blue = '0%s' % blue blue = "0%s" % blue
# Assemable the bg color "string" in webnotation. Eg. '#d3d3d3' # Assemable the bg color "string" in webnotation. Eg. '#d3d3d3'
bg_color = '#' + red + green + blue bg_color = "#" + red + green + blue
# Image # Image
image = Image.new('RGB', (size, size)) image = Image.new("RGB", (size, size))
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
# Draw background # Draw background
draw.rectangle(((0, 0), (size, size)), fill=bg_color) draw.rectangle(((0, 0), (size, size)), fill=bg_color)
# Draw MMs head # Draw MMs head
draw.ellipse((start_x, start_y, end_x, end_y), fill='white') draw.ellipse((start_x, start_y, end_x, end_y), fill="white")
# Draw MMs 'body' # Draw MMs 'body'
draw.polygon(( draw.polygon(
(
(start_x + circlesize / 2, size / 2.5), (start_x + circlesize / 2, size / 2.5),
(size * 0.15, size), (size * 0.15, size),
(size-size*0.15, size)), (size - size * 0.15, size),
fill='white') ),
fill="white",
)
return image return image

View File

@@ -13,7 +13,7 @@ from django.http import HttpResponse, HttpResponseRedirect
from django.http import HttpResponseNotFound, JsonResponse from django.http import HttpResponseNotFound, JsonResponse
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core.cache import cache, caches from django.core.cache import cache, caches
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.db.models import Q from django.db.models import Q
from django.contrib.auth.models import User from django.contrib.auth.models import User

View File

@@ -1,21 +1,26 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*-
import urllib.request import urllib.request
import sys import sys
import os import os
sys.stderr.buffer.write(b'%s' % bytes(os.environ.get("QUERY_STRING", "No Query String in url"), 'utf-8')) sys.stderr.buffer.write(
b"%s" % bytes(os.environ.get("QUERY_STRING", "No Query String in url"), "utf-8")
)
link = 'https://www.libravatar.org/avatar/%s' % os.environ.get("QUERY_STRING", 'x'*32) link = "https://www.libravatar.org/avatar/%s" % os.environ.get("QUERY_STRING", "x" * 32)
sys.stderr.buffer.write(b'%s' % bytes(link, 'utf-8')) sys.stderr.buffer.write(b"%s" % bytes(link, "utf-8"))
data = None data = None
with urllib.request.urlopen(link) as f: with urllib.request.urlopen(link) as f:
data = f.read() data = f.read()
for header in f.headers._headers: for header in f.headers._headers:
if header[0] == 'Content-Type': if header[0] == "Content-Type":
sys.stdout.buffer.write(b"%s: %s\n\n" % (bytes(header[0], 'utf-8'), bytes(header[1], 'utf-8'))) sys.stdout.buffer.write(
b"%s: %s\n\n" % (bytes(header[0], "utf-8"), bytes(header[1], "utf-8"))
)
sys.stdout.flush() sys.stdout.flush()
break break

View File

@@ -1,7 +1,7 @@
autopep8 autopep8
bcrypt bcrypt
defusedxml defusedxml
Django < 4 Django < 4.0
django-anymail[mailgun] django-anymail[mailgun]
django-auth-ldap django-auth-ldap
django-bootstrap4 django-bootstrap4