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 sys
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 ivatar.settings import BASE_DIR

View File

@@ -1,7 +1,8 @@
#!/usr/bin/env python
'''
# -*- coding: utf-8 -*-
"""
Import the whole libravatar export
'''
"""
import os
from os.path import isfile, isdir, join
@@ -9,13 +10,18 @@ import sys
import base64
from io import BytesIO
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
from django.contrib.auth.models import User
from PIL import Image
from django_openid_auth.models import UserOpenID
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 ConfirmedOpenId
from ivatar.ivataraccount.models import Photo
@@ -26,54 +32,63 @@ if len(sys.argv) < 2:
exit(-255)
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)
PATH = sys.argv[1]
for file in os.listdir(PATH):
if not file.endswith('.xml.gz'):
if not file.endswith(".xml.gz"):
continue
if isfile(join(PATH, file)):
fh = open(join(PATH, file), 'rb')
fh = open(join(PATH, file), "rb")
items = libravatar_read_gzdata(fh.read())
print('Adding user "%s"' % items['username'])
(user, created) = User.objects.get_or_create(username=items['username'])
user.password = items['password']
print('Adding user "%s"' % items["username"])
(user, created) = User.objects.get_or_create(username=items["username"])
user.password = items["password"]
user.save()
saved_photos = {}
for photo in items['photos']:
photo_id = photo['id']
data = base64.decodebytes(bytes(photo['data'], 'utf-8'))
for photo in items["photos"]:
photo_id = photo["id"]
data = base64.decodebytes(bytes(photo["data"], "utf-8"))
pilobj = Image.open(BytesIO(data))
out = BytesIO()
pilobj.save(out, pilobj.format, quality=JPEG_QUALITY)
out.seek(0)
photo = Photo()
photo.user = user
photo.ip_address = '0.0.0.0'
photo.ip_address = "0.0.0.0"
photo.format = file_format(pilobj.format)
photo.data = out.read()
photo.save()
saved_photos[photo_id] = photo
for email in items['emails']:
for email in items["emails"]:
try:
ConfirmedEmail.objects.get_or_create(email=email['email'], user=user,
photo=saved_photos.get(email['photo_id']))
except django.db.utils.IntegrityError:
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'],
ConfirmedEmail.objects.get_or_create(
email=email["email"],
user=user,
photo=saved_photos.get(email["photo_id"]),
)
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()

View File

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

View File

@@ -1,12 +1,13 @@
'''
# -*- coding: utf-8 -*-
"""
Register models in admin
'''
"""
from django.contrib import admin
from . models import Photo, ConfirmedEmail, UnconfirmedEmail
from . models import ConfirmedOpenId, UnconfirmedOpenId
from . models import OpenIDNonce, OpenIDAssociation
from . models import UserPreference
from .models import Photo, ConfirmedEmail, UnconfirmedEmail
from .models import ConfirmedOpenId, UnconfirmedOpenId
from .models import OpenIDNonce, OpenIDAssociation
from .models import UserPreference
# Register models in admin
admin.site.register(Photo)

View File

@@ -1,112 +1,117 @@
'''
# -*- coding: utf-8 -*-
"""
Classes for our ivatar.ivataraccount.forms
'''
"""
from urllib.parse import urlsplit, urlunsplit
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 ivatar import settings
from ivatar.settings import MIN_LENGTH_EMAIL, MAX_LENGTH_EMAIL
from ivatar.settings import MIN_LENGTH_URL, MAX_LENGTH_URL
from . models import UnconfirmedEmail, ConfirmedEmail, Photo
from . models import UnconfirmedOpenId, ConfirmedOpenId
from . models import UserPreference
from .models import UnconfirmedEmail, ConfirmedEmail, Photo
from .models import UnconfirmedOpenId, ConfirmedOpenId
from .models import UserPreference
MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5
class AddEmailForm(forms.Form):
'''
"""
Form to handle adding email addresses
'''
"""
email = forms.EmailField(
label=_('Email'),
label=_("Email"),
min_length=MIN_LENGTH_EMAIL,
max_length=MAX_LENGTH_EMAIL,
)
def clean_email(self):
'''
"""
Enforce lowercase email
'''
"""
# TODO: Domain restriction as in libravatar?
return self.cleaned_data['email'].lower()
return self.cleaned_data["email"].lower()
def save(self, request):
'''
"""
Save the model, ensuring some safety
'''
"""
user = request.user
# Enforce the maximum number of unconfirmed emails a user can have
num_unconfirmed = user.unconfirmedemail_set.count()
max_num_unconfirmed_emails = getattr(
settings,
'MAX_NUM_UNCONFIRMED_EMAILS',
MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT)
settings, "MAX_NUM_UNCONFIRMED_EMAILS", MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT
)
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
# Check whether or not a confirmation email has been
# sent by this user already
if UnconfirmedEmail.objects.filter( # pylint: disable=no-member
user=user, email=self.cleaned_data['email']).exists():
self.add_error(
'email',
_('Address already added, currently unconfirmed'))
user=user, email=self.cleaned_data["email"]
).exists():
self.add_error("email", _("Address already added, currently unconfirmed"))
return False
# Check whether or not the email is already confirmed (by someone)
check_mail = ConfirmedEmail.objects.filter(
email=self.cleaned_data['email'])
check_mail = ConfirmedEmail.objects.filter(email=self.cleaned_data["email"])
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:
msg = _('Address already confirmed (by you)')
self.add_error('email', msg)
msg = _("Address already confirmed (by you)")
self.add_error("email", msg)
return False
unconfirmed = UnconfirmedEmail()
unconfirmed.email = self.cleaned_data['email']
unconfirmed.email = self.cleaned_data["email"]
unconfirmed.user = user
unconfirmed.save()
unconfirmed.send_confirmation_mail(url=request.build_absolute_uri('/')[:-1])
unconfirmed.send_confirmation_mail(url=request.build_absolute_uri("/")[:-1])
return True
class UploadPhotoForm(forms.Form):
'''
"""
Form handling photo upload
'''
"""
photo = forms.FileField(
label=_('Photo'),
error_messages={'required': _('You must choose an image to upload.')})
label=_("Photo"),
error_messages={"required": _("You must choose an image to upload.")},
)
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,
error_messages={
'required':
_('We only host "G-rated" images and so this field must be checked.')
})
"required": _(
'We only host "G-rated" images and so this field must be checked.'
)
},
)
can_distribute = forms.BooleanField(
label=_('can be freely copied'),
label=_("can be freely copied"),
required=True,
error_messages={
'required':
_('This field must be checked since we need to be able to distribute photos to third parties.')
})
"required": _(
"This field must be checked since we need to be able to distribute photos to third parties."
)
},
)
@staticmethod
def save(request, data):
'''
"""
Save the model and assign it to the current user
'''
"""
# Link this file to the user's profile
photo = Photo()
photo.user = request.user
@@ -119,47 +124,48 @@ class UploadPhotoForm(forms.Form):
class AddOpenIDForm(forms.Form):
'''
"""
Form to handle adding OpenID
'''
"""
openid = forms.URLField(
label=_('OpenID'),
label=_("OpenID"),
min_length=MIN_LENGTH_URL,
max_length=MAX_LENGTH_URL,
initial='http://'
initial="http://",
)
def clean_openid(self):
'''
"""
Enforce restrictions
'''
"""
# Lowercase hostname port of the URL
url = urlsplit(self.cleaned_data['openid'])
url = urlsplit(self.cleaned_data["openid"])
data = urlunsplit(
(url.scheme.lower(), url.netloc.lower(), url.path,
url.query, url.fragment))
(url.scheme.lower(), url.netloc.lower(), url.path, url.query, url.fragment)
)
# TODO: Domain restriction as in libravatar?
return data
def save(self, user):
'''
"""
Save the model, ensuring some safety
'''
"""
if ConfirmedOpenId.objects.filter( # pylint: disable=no-member
openid=self.cleaned_data['openid']).exists():
self.add_error('openid', _('OpenID already added and confirmed!'))
openid=self.cleaned_data["openid"]
).exists():
self.add_error("openid", _("OpenID already added and confirmed!"))
return False
if UnconfirmedOpenId.objects.filter( # pylint: disable=no-member
openid=self.cleaned_data['openid']).exists():
self.add_error(
'openid',
_('OpenID already added, but not confirmed yet!'))
openid=self.cleaned_data["openid"]
).exists():
self.add_error("openid", _("OpenID already added, but not confirmed yet!"))
return False
unconfirmed = UnconfirmedOpenId()
unconfirmed.openid = self.cleaned_data['openid']
unconfirmed.openid = self.cleaned_data["openid"]
unconfirmed.user = user
unconfirmed.save()
@@ -167,40 +173,50 @@ class AddOpenIDForm(forms.Form):
class UpdatePreferenceForm(forms.ModelForm):
'''
"""
Form for updating user preferences
'''
"""
class Meta: # pylint: disable=too-few-public-methods
'''
"""
Meta class for UpdatePreferenceForm
'''
"""
model = UserPreference
fields = ['theme']
fields = ["theme"]
class UploadLibravatarExportForm(forms.Form):
'''
"""
Form handling libravatar user export upload
'''
"""
export_file = forms.FileField(
label=_('Export file'),
error_messages={'required': _('You must choose an export file to upload.')})
label=_("Export file"),
error_messages={"required": _("You must choose an export file to upload.")},
)
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,
error_messages={
'required':
_('We only host "G-rated" images and so this field must be checked.')
})
"required": _(
'We only host "G-rated" images and so this field must be checked.'
)
},
)
can_distribute = forms.BooleanField(
label=_('can be freely copied'),
label=_("can be freely copied"),
required=True,
error_messages={
'required':
_('This field must be checked since we need to be able to\
distribute photos to third parties.')
})
"required": _(
"This field must be checked since we need to be able to\
distribute photos to third parties."
)
},
)
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,53 +1,58 @@
'''
# -*- coding: utf-8 -*-
"""
Helper method to fetch Gravatar image
'''
"""
from ssl import SSLError
from urllib.request import urlopen, HTTPError, URLError
import hashlib
from .. settings import AVATAR_MAX_SIZE
from ..settings import AVATAR_MAX_SIZE
URL_TIMEOUT = 5 # in seconds
def get_photo(email):
'''
"""
Fetch photo from Gravatar, given an email address
'''
hash_object = hashlib.new('md5')
hash_object.update(email.lower().encode('utf-8'))
thumbnail_url = 'https://secure.gravatar.com/avatar/' + \
hash_object.hexdigest() + '?s=%i&d=404' % AVATAR_MAX_SIZE
image_url = 'https://secure.gravatar.com/avatar/' + hash_object.hexdigest(
) + '?s=512&d=404'
"""
hash_object = hashlib.new("md5")
hash_object.update(email.lower().encode("utf-8"))
thumbnail_url = (
"https://secure.gravatar.com/avatar/"
+ hash_object.hexdigest()
+ "?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
service_url = 'http://www.gravatar.com/' + hash_object.hexdigest()
service_url = "http://www.gravatar.com/" + hash_object.hexdigest()
try:
urlopen(image_url, timeout=URL_TIMEOUT)
except HTTPError as exc:
if exc.code != 404 and exc.code != 503:
print( # pragma: no cover
'Gravatar fetch failed with an unexpected %s HTTP error' %
exc.code)
"Gravatar fetch failed with an unexpected %s HTTP error" % exc.code
)
return False
except URLError as exc: # pragma: no cover
print(
'Gravatar fetch failed with URL error: %s' %
exc.reason) # pragma: no cover
"Gravatar fetch failed with URL error: %s" % exc.reason
) # pragma: no cover
return False # pragma: no cover
except SSLError as exc: # pragma: no cover
except SSLError as exc: # pragma: no cover
print(
'Gravatar fetch failed with SSL error: %s' %
exc.reason) # pragma: no cover
"Gravatar fetch failed with SSL error: %s" % exc.reason
) # pragma: no cover
return False # pragma: no cover
return {
'thumbnail_url': thumbnail_url,
'image_url': image_url,
'width': AVATAR_MAX_SIZE,
'height': AVATAR_MAX_SIZE,
'service_url': service_url,
'service_name': 'Gravatar'
"thumbnail_url": thumbnail_url,
"image_url": image_url,
"width": AVATAR_MAX_SIZE,
"height": AVATAR_MAX_SIZE,
"service_url": service_url,
"service_name": "Gravatar",
}

View File

@@ -19,7 +19,7 @@ from django.db import models
from django.utils import timezone
from django.http import HttpResponseRedirect
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.mail import send_mail
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 (
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.urls import reverse_lazy, reverse
from django.shortcuts import render

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
"""
Django settings for ivatar project.
"""
@@ -6,7 +7,7 @@ import os
import logging
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)
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!
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!
DEBUG = True
@@ -25,52 +26,52 @@ ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = 'ivatar.urls'
ROOT_URLCONF = "ivatar.urls"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = 'ivatar.wsgi.application'
WSGI_APPLICATION = "ivatar.wsgi.application"
# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
}
@@ -80,16 +81,16 @@ DATABASES = {
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
# 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
@@ -109,15 +110,10 @@ USE_TZ = True
# Static files configuration (esp. req. during dev.)
PROJECT_ROOT = os.path.abspath(
os.path.join(
os.path.dirname(__file__),
os.pardir
)
)
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), 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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
'''
# -*- coding: utf-8 -*-
"""
Test our utils from ivatar.utils
'''
"""
from django.test import TestCase
@@ -8,18 +9,18 @@ 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'
"""
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)

View File

@@ -1,6 +1,7 @@
'''
# -*- coding: utf-8 -*-
"""
Test our views in ivatar.ivataraccount.views and ivatar.views
'''
"""
# pylint: disable=too-many-lines
import os
import django
@@ -10,33 +11,34 @@ from django.contrib.auth.models import User
from ivatar.utils import random_string
os.environ['DJANGO_SETTINGS_MODULE'] = 'ivatar.settings'
os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
django.setup()
class Tester(TestCase): # pylint: disable=too-many-public-methods
'''
"""
Main test class
'''
"""
client = Client()
user = None
username = 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
openid = 'http://%s.%s.%s/' % (username, random_string(), 'org')
openid = "http://%s.%s.%s/" % (username, random_string(), "org")
def login(self):
'''
"""
Login as user
'''
"""
self.client.login(username=self.username, password=self.password)
def setUp(self):
'''
"""
Prepare for tests.
- Create user
'''
"""
self.user = User.objects.create_user(
username=self.username,
password=self.password,
@@ -46,8 +48,9 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"""
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(
response=response,
expected_url='/static/img/deadbeef.png',
msg_prefix='Why does an invalid hash not redirect to deadbeef?')
expected_url="/static/img/deadbeef.png",
msg_prefix="Why does an invalid hash not redirect to deadbeef?",
)

View File

@@ -1,22 +1,27 @@
'''
# -*- coding: utf-8 -*-
"""
Unit tests for WSGI
'''
"""
import unittest
import os
import django
os.environ['DJANGO_SETTINGS_MODULE'] = 'ivatar.settings'
os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
django.setup()
class TestCase(unittest.TestCase):
'''
"""
Simple testcase to see if WSGI loads correctly
'''
"""
def test_run_wsgi(self):
'''
"""
Run wsgi import
'''
"""
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
'''
"""
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.forms.utils import ErrorList
@@ -12,45 +13,40 @@ from ivatar.settings import MIN_LENGTH_EMAIL, MAX_LENGTH_EMAIL
class CheckDomainForm(forms.Form):
'''
"""
Form handling domain check
'''
"""
domain = forms.CharField(
label=_('Domain'),
label=_("Domain"),
required=True,
error_messages={
'required':
_('Cannot check without a domain name.')
}
error_messages={"required": _("Cannot check without a domain name.")},
)
class CheckForm(forms.Form):
'''
"""
Form handling check
'''
"""
mail = forms.EmailField(
label=_('E-Mail'),
label=_("E-Mail"),
required=False,
min_length=MIN_LENGTH_EMAIL,
max_length=MAX_LENGTH_EMAIL,
error_messages={
'required':
_('Cannot check without a domain name.')
})
error_messages={"required": _("Cannot check without a domain name.")},
)
openid = forms.CharField(
label=_('OpenID'),
label=_("OpenID"),
required=False,
min_length=MIN_LENGTH_URL,
max_length=MAX_LENGTH_URL,
error_messages={
'required':
_('Cannot check without an openid name.')
})
error_messages={"required": _("Cannot check without an openid name.")},
)
size = forms.IntegerField(
label=_('Size'),
label=_("Size"),
initial=80,
min_value=5,
max_value=AVATAR_MAX_SIZE,
@@ -58,24 +54,24 @@ class CheckForm(forms.Form):
)
default_opt = forms.ChoiceField(
label=_('Default'),
label=_("Default"),
required=False,
widget=forms.RadioSelect,
choices = [
('retro', _('Retro style (similar to GitHub)')),
('robohash', _('Roboter style')),
('pagan', _('Retro adventure character')),
('wavatar', _('Wavatar style')),
('monsterid', _('Monster style')),
('identicon', _('Identicon style')),
('mm', _('Mystery man')),
('mmng', _('Mystery man NextGen')),
('none', _('None')),
choices=[
("retro", _("Retro style (similar to GitHub)")),
("robohash", _("Roboter style")),
("pagan", _("Retro adventure character")),
("wavatar", _("Wavatar style")),
("monsterid", _("Monster style")),
("identicon", _("Identicon style")),
("mm", _("Mystery man")),
("mmng", _("Mystery man NextGen")),
("none", _("None")),
],
)
default_url = forms.URLField(
label=_('Default URL'),
label=_("Default URL"),
min_length=1,
max_length=MAX_LENGTH_URL,
required=False,
@@ -83,28 +79,27 @@ class CheckForm(forms.Form):
def clean(self):
self.cleaned_data = super().clean()
mail = self.cleaned_data.get('mail')
openid = self.cleaned_data.get('openid')
default_url = self.cleaned_data.get('default_url')
default_opt = self.cleaned_data.get('default_opt')
if default_url and default_opt and default_opt != 'none':
if not 'default_url' in self._errors:
self._errors['default_url'] = ErrorList()
if not 'default_opt' in self._errors:
self._errors['default_opt'] = ErrorList()
mail = self.cleaned_data.get("mail")
openid = self.cleaned_data.get("openid")
default_url = self.cleaned_data.get("default_url")
default_opt = self.cleaned_data.get("default_opt")
if default_url and default_opt and default_opt != "none":
if "default_url" not in self._errors:
self._errors["default_url"] = ErrorList()
if "default_opt" not in self._errors:
self._errors["default_opt"] = ErrorList()
errstring = _('Only default URL OR default keyword may be specified')
self._errors['default_url'].append(errstring)
self._errors['default_opt'].append(errstring)
errstring = _("Only default URL OR default keyword may be specified")
self._errors["default_url"].append(errstring)
self._errors["default_opt"].append(errstring)
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
def clean_openid(self):
data = self.cleaned_data['openid']
data = self.cleaned_data["openid"]
return data.lower()
def clean_mail(self):
data = self.cleaned_data['mail']
print(data)
data = self.cleaned_data["mail"]
return data.lower()

View File

@@ -1,57 +1,48 @@
'''
# -*- coding: utf-8 -*-
"""
Test our views in ivatar.ivataraccount.views and ivatar.views
'''
"""
# pylint: disable=too-many-lines
from urllib.parse import urlsplit
from io import BytesIO
import io
import os
import django
from django.test import TestCase
from django.test import Client
from django.urls import reverse
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
import hashlib
from libravatar import libravatar_url
from PIL import Image
os.environ['DJANGO_SETTINGS_MODULE'] = 'ivatar.settings'
os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
django.setup()
# 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
# pylint: enable=wrong-import-position
class Tester(TestCase): # pylint: disable=too-many-public-methods
'''
"""
Main test class
'''
"""
client = Client()
user = None
username = 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
openid = 'http://%s.%s.%s/' % (username, random_string(), 'org')
openid = "http://%s.%s.%s/" % (username, random_string(), "org")
def login(self):
'''
"""
Login as user
'''
"""
self.client.login(username=self.username, password=self.password)
def setUp(self):
'''
"""
Prepare for tests.
- Create user
'''
"""
self.user = User.objects.create_user(
username=self.username,
password=self.password,
@@ -61,12 +52,12 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"""
Test check page
"""
response = self.client.get(reverse('tools_check'))
self.assertEqual(response.status_code, 200, 'no 200 ok?')
response = self.client.get(reverse("tools_check"))
self.assertEqual(response.status_code, 200, "no 200 ok?")
def test_check_domain(self):
"""
Test check domain page
"""
response = self.client.get(reverse('tools_check_domain'))
self.assertEqual(response.status_code, 200, 'no 200 ok?')
response = self.client.get(reverse("tools_check_domain"))
self.assertEqual(response.status_code, 200, "no 200 ok?")

View File

@@ -1,12 +1,13 @@
'''
# -*- coding: utf-8 -*-
"""
ivatar/tools URL configuration
'''
"""
from django.conf.urls import url
from . views import CheckView, CheckDomainView
from .views import CheckView, CheckDomainView
urlpatterns = [ # pylint: disable=invalid-name
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/", 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"),
]

View File

@@ -1,6 +1,7 @@
'''
# -*- coding: utf-8 -*-
"""
View classes for ivatar/tools/
'''
"""
from socket import inet_ntop, AF_INET6
import hashlib
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 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):
'''
"""
View class for checking a domain
'''
template_name = 'check_domain.html'
"""
template_name = "check_domain.html"
form_class = CheckDomainForm
success_url = reverse('tools_check_domain')
success_url = reverse("tools_check_domain")
def form_valid(self, form):
result = {}
super().form_valid(form)
domain = form.cleaned_data['domain']
result['avatar_server_http'] = lookup_avatar_server(domain, False)
if result['avatar_server_http']:
result['avatar_server_http_ipv4'] = lookup_ip_address(
result['avatar_server_http'],
False)
result['avatar_server_http_ipv6'] = lookup_ip_address(
result['avatar_server_http'],
True)
result['avatar_server_https'] = lookup_avatar_server(domain, True)
if result['avatar_server_https']:
result['avatar_server_https_ipv4'] = lookup_ip_address(
result['avatar_server_https'],
False)
result['avatar_server_https_ipv6'] = lookup_ip_address(
result['avatar_server_https'],
True)
return render(self.request, self.template_name, {
'form': form,
'result': result,
})
domain = form.cleaned_data["domain"]
result["avatar_server_http"] = lookup_avatar_server(domain, False)
if result["avatar_server_http"]:
result["avatar_server_http_ipv4"] = lookup_ip_address(
result["avatar_server_http"], False
)
result["avatar_server_http_ipv6"] = lookup_ip_address(
result["avatar_server_http"], True
)
result["avatar_server_https"] = lookup_avatar_server(domain, True)
if result["avatar_server_https"]:
result["avatar_server_https_ipv4"] = lookup_ip_address(
result["avatar_server_https"], False
)
result["avatar_server_https_ipv6"] = lookup_ip_address(
result["avatar_server_https"], True
)
return render(
self.request,
self.template_name,
{
"form": form,
"result": result,
},
)
class CheckView(FormView):
'''
"""
View class for checking an e-mail or openid address
'''
template_name = 'check.html'
"""
template_name = "check.html"
form_class = CheckForm
success_url = reverse('tools_check')
success_url = reverse("tools_check")
def form_valid(self, form):
mailurl = None
@@ -73,82 +84,88 @@ class CheckView(FormView):
super().form_valid(form)
if 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':
default_url = form.cleaned_data['default_opt']
if 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"
):
default_url = form.cleaned_data["default_opt"]
else:
default_url = None
if 'size' in form.cleaned_data:
size = form.cleaned_data['size']
if form.cleaned_data['mail']:
if "size" in form.cleaned_data:
size = form.cleaned_data["size"]
if form.cleaned_data["mail"]:
mailurl = libravatar_url(
email=form.cleaned_data['mail'],
size=size,
default=default_url)
email=form.cleaned_data["mail"], size=size, default=default_url
)
mailurl = mailurl.replace(LIBRAVATAR_BASE_URL, BASE_URL)
mailurl_secure = libravatar_url(
email=form.cleaned_data['mail'],
email=form.cleaned_data["mail"],
size=size,
https=True,
default=default_url)
default=default_url,
)
mailurl_secure = mailurl_secure.replace(
LIBRAVATAR_SECURE_BASE_URL,
SECURE_BASE_URL)
LIBRAVATAR_SECURE_BASE_URL, SECURE_BASE_URL
)
mail_hash = parse_user_identity(
email=form.cleaned_data['mail'],
openid=None)[0]
hash_obj = hashlib.new('sha256')
hash_obj.update(form.cleaned_data['mail'].encode('utf-8'))
email=form.cleaned_data["mail"], openid=None
)[0]
hash_obj = hashlib.new("sha256")
hash_obj.update(form.cleaned_data["mail"].encode("utf-8"))
mail_hash256 = hash_obj.hexdigest()
mailurl_secure_256 = mailurl_secure.replace(
mail_hash,
mail_hash256)
if form.cleaned_data['openid']:
if not form.cleaned_data['openid'].startswith('http://') and \
not form.cleaned_data['openid'].startswith('https://'):
form.cleaned_data['openid'] = 'http://%s' % form.cleaned_data['openid']
mailurl_secure_256 = mailurl_secure.replace(mail_hash, mail_hash256)
if form.cleaned_data["openid"]:
if not form.cleaned_data["openid"].startswith(
"http://"
) and not form.cleaned_data["openid"].startswith("https://"):
form.cleaned_data["openid"] = "http://%s" % form.cleaned_data["openid"]
openidurl = libravatar_url(
openid=form.cleaned_data['openid'],
size=size,
default=default_url)
openid=form.cleaned_data["openid"], size=size, default=default_url
)
openidurl = openidurl.replace(LIBRAVATAR_BASE_URL, BASE_URL)
openidurl_secure = libravatar_url(
openid=form.cleaned_data['openid'],
openid=form.cleaned_data["openid"],
size=size,
https=True,
default=default_url)
default=default_url,
)
openidurl_secure = openidurl_secure.replace(
LIBRAVATAR_SECURE_BASE_URL,
SECURE_BASE_URL)
LIBRAVATAR_SECURE_BASE_URL, SECURE_BASE_URL
)
openid_hash = parse_user_identity(
openid=form.cleaned_data['openid'],
email=None)[0]
openid=form.cleaned_data["openid"], email=None
)[0]
return render(self.request, self.template_name, {
'form': form,
'mailurl': mailurl,
'openidurl': openidurl,
'mailurl_secure': mailurl_secure,
'mailurl_secure_256': mailurl_secure_256,
'openidurl_secure': openidurl_secure,
'mail_hash': mail_hash,
'mail_hash256': mail_hash256,
'openid_hash': openid_hash,
'size': size,
})
return render(
self.request,
self.template_name,
{
"form": form,
"mailurl": mailurl,
"openidurl": openidurl,
"mailurl_secure": mailurl_secure,
"mailurl_secure_256": mailurl_secure_256,
"openidurl_secure": openidurl_secure,
"mail_hash": mail_hash,
"mail_hash256": mail_hash256,
"openid_hash": openid_hash,
"size": size,
},
)
def lookup_avatar_server(domain, https):
'''
"""
Extract the avatar server from an SRV record in the DNS zone
The SRV records should look like this:
_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
'''
"""
if domain and len(domain) > 60:
domain = domain[:60]
@@ -161,27 +178,35 @@ def lookup_avatar_server(domain, https):
DNS.DiscoverNameServers()
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:
print("DNS Error: %s (%s)" % (message, domain))
return None
if dns_request.header['status'] == 'NXDOMAIN':
if dns_request.header["status"] == "NXDOMAIN":
# Not an error, but no point in going any further
return None
if dns_request.header['status'] != 'NOERROR':
print("DNS Error: status=%s (%s)" % (dns_request.header['status'], domain))
if dns_request.header["status"] != "NOERROR":
print("DNS Error: status=%s (%s)" % (dns_request.header["status"], domain))
return None
records = []
for answer in dns_request.answers:
if ('data' not in answer) or (not answer['data']) or \
(not answer['typename']) or (answer['typename'] != 'SRV'):
if (
("data" not in answer)
or (not answer["data"])
or (not answer["typename"])
or (answer["typename"] != "SRV")
):
continue
record = {'priority': int(answer['data'][0]), 'weight': int(answer['data'][1]),
'port': int(answer['data'][2]), 'target': answer['data'][3]}
record = {
"priority": int(answer["data"][0]),
"weight": int(answer["data"][1]),
"port": int(answer["data"][2]),
"target": answer["data"][3],
}
records.append(record)
@@ -194,38 +219,38 @@ def lookup_avatar_server(domain, https):
def srv_hostname(records):
'''
"""
Return the right (target, port) pair from a list of SRV records.
'''
"""
if len(records) < 1:
return (None, None)
if len(records) == 1:
ret = records[0]
return (ret['target'], ret['port'])
return (ret["target"], ret["port"])
# Keep only the servers in the top priority
priority_records = []
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:
if ret['priority'] > top_priority:
if ret["priority"] > top_priority:
# ignore the record (ret has lower priority)
continue
# Take care - this if is only a if, if the above if
# 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)
top_priority = ret['priority']
top_priority = ret["priority"]
total_weight = 0
priority_records = []
total_weight += ret['weight']
total_weight += ret["weight"]
if ret['weight'] > 0:
if ret["weight"] > 0:
priority_records.append((total_weight, ret))
else:
# zero-weigth elements must come first
@@ -233,7 +258,7 @@ def srv_hostname(records):
if len(priority_records) == 1:
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)
random_number = random.randint(0, total_weight)
@@ -242,9 +267,9 @@ def srv_hostname(records):
weighted_index, ret = record
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)
@@ -263,19 +288,21 @@ def lookup_ip_address(hostname, ipv6):
print("DNS Error: %s (%s)" % (message, hostname))
return None
if dns_request.header['status'] != 'NOERROR':
print("DNS Error: status=%s (%s)" % (dns_request.header['status'], hostname))
if dns_request.header["status"] != "NOERROR":
print("DNS Error: status=%s (%s)" % (dns_request.header["status"], hostname))
return None
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
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
if ipv6:
return inet_ntop(AF_INET6, answer['data'])
return inet_ntop(AF_INET6, answer["data"])
return answer['data']
return answer["data"]
return None

View File

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

View File

@@ -1,59 +1,65 @@
'''
# -*- coding: utf-8 -*-
"""
Simple module providing reusable random_string function
'''
"""
import random
import string
from PIL import Image, ImageDraw
def random_string(length=10):
'''
"""
Return some random string with default length 10
'''
return ''.join(random.SystemRandom().choice(
string.ascii_lowercase + string.digits) for _ in range(length))
"""
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 + '/'
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://')
var2 = openid.replace("http://", "https://")
var3 = var2[0:-1]
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
add some red, green or blue, if specified
'''
"""
# Make sure the lightest bg color we paint is e0, else
# we do not see the MM any more
if idhash[0] == 'f':
idhash = 'e0'
if idhash[0] == "f":
idhash = "e0"
# How large is the circle?
circlesize = size*0.6
circlesize = size * 0.6
# Coordinates for the circle
start_x = int(size*0.2)
end_x = start_x+circlesize
start_y = int(size*0.05)
end_y = start_y+circlesize
start_x = int(size * 0.2)
end_x = start_x + circlesize
start_y = int(size * 0.05)
end_y = start_y + circlesize
# All are the same, based on the input hash
# this should always result in a "gray-ish" background
@@ -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]
# 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:
red = 'ff'
red = "ff"
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
green = hex(int(green, 16)+add_green).replace('0x', '')
green = hex(int(green, 16) + add_green).replace("0x", "")
if int(green, 16) > 255:
green = 'ff'
green = "ff"
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
blue = hex(int(blue, 16)+add_blue).replace('0x', '')
blue = hex(int(blue, 16) + add_blue).replace("0x", "")
if int(blue, 16) > 255:
blue = 'ff'
blue = "ff"
if len(blue) == 1:
blue = '0%s' % blue
blue = "0%s" % blue
# Assemable the bg color "string" in webnotation. Eg. '#d3d3d3'
bg_color = '#' + red + green + blue
bg_color = "#" + red + green + blue
# Image
image = Image.new('RGB', (size, size))
image = Image.new("RGB", (size, size))
draw = ImageDraw.Draw(image)
# Draw background
draw.rectangle(((0, 0), (size, size)), fill=bg_color)
# 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.polygon((
(start_x+circlesize/2, size/2.5),
(size*0.15, size),
(size-size*0.15, size)),
fill='white')
draw.polygon(
(
(start_x + circlesize / 2, size / 2.5),
(size * 0.15, size),
(size - size * 0.15, size),
),
fill="white",
)
return image

View File

@@ -13,7 +13,7 @@ from django.http import HttpResponse, HttpResponseRedirect
from django.http import HttpResponseNotFound, JsonResponse
from django.core.exceptions import ObjectDoesNotExist
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.db.models import Q
from django.contrib.auth.models import User

View File

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

View File

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