...
 
Commits (114)
[run]
plugins =
django_coverage_plugin
omit =
node_modules/*
.virtualenv/*
import_libravatar.py
[html]
extra_css = coverage_extra_style.css
......@@ -11,3 +11,5 @@ htmlcov/
.ropeproject/
db.sqlite3.SAVE
node_modules/
config_local.py
locale/*/LC_MESSAGES/django.mo
image: centos:centos7
image: ofalk/centos7-python36
before_script:
- yum install -y -t https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
- yum -y -t install python36 python34-pip python36-devel unzip mysql-devel gcc git openldap-devel
- pip3 install pip --upgrade
- pip install virtualenv --upgrade
- virtualenv -p python3.6 /tmp/.virtualenv
- source /tmp/.virtualenv/bin/activate
- pip install Pillow
- pip install -r requirements.txt
- pip install python-coveralls
- pip install coverage
......@@ -16,6 +13,9 @@ before_script:
test_and_coverage:
stage: test
script:
- echo 'from ivatar.settings import TEMPLATES' > config_local.py
- echo 'TEMPLATES[0]["OPTIONS"]["debug"] = True' >> config_local.py
- echo "DEBUG = True" >> config_local.py
- python manage.py collectstatic --noinput
- coverage run --source . manage.py test -v3
- coverage report --fail-under=70
......
......@@ -5,6 +5,8 @@ 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.contrib.messages import constants as message_constants
from ivatar.settings import BASE_DIR
from ivatar.settings import MIDDLEWARE
......@@ -49,8 +51,8 @@ TEMPLATES[0]['OPTIONS']['context_processors'].append(
OPENID_CREATE_USERS = True
OPENID_UPDATE_DETAILS_FROM_SREG = True
SITE_NAME = os.environ.get('SITE_NAME', 'ivatar')
IVATAR_VERSION = '0.1'
SITE_NAME = os.environ.get('SITE_NAME', 'libravatar')
IVATAR_VERSION = '1.1'
SECURE_BASE_URL = os.environ.get('SECURE_BASE_URL', 'https://avatars.linux-kernel.at/avatar/')
BASE_URL = os.environ.get('BASE_URL', 'http://avatars.linux-kernel.at/avatar/')
......@@ -95,16 +97,19 @@ BOOTSTRAP4 = {
}
if 'EMAIL_BACKEND' in os.environ:
EMAIL_BACKEND = os.environ['EMAIL_BACKEND']
EMAIL_BACKEND = os.environ['EMAIL_BACKEND'] # pragma: no cover
else:
if 'test' in sys.argv or 'collectstatic' in sys.argv:
EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
else:
ANYMAIL = { # pragma: no cover
'MAILGUN_API_KEY': os.environ['IVATAR_MAILGUN_API_KEY'],
'MAILGUN_SENDER_DOMAIN': os.environ['IVATAR_MAILGUN_SENDER_DOMAIN'],
}
EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend' # pragma: no cover
try:
ANYMAIL = { # pragma: no cover
'MAILGUN_API_KEY': os.environ['IVATAR_MAILGUN_API_KEY'],
'MAILGUN_SENDER_DOMAIN': os.environ['IVATAR_MAILGUN_SENDER_DOMAIN'],
}
EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend' # pragma: no cover
except Exception as exc:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
SERVER_EMAIL = os.environ.get('SERVER_EMAIL', 'ivatar@mg.linux-kernel.at')
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'ivatar@mg.linux-kernel.at')
......@@ -147,3 +152,38 @@ USE_X_FORWARDED_HOST = True
ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS = ['avatars.linux-kernel.at', 'localhost',]
DEFAULT_AVATAR_SIZE = 80
LANGUAGES = (
('de', _('Deutsch')),
('en', _('English')),
('ca', _('Català')),
('cs', _('Česky')),
('es', _('Español')),
('eu', _('Basque')),
('fr', _('Français')),
('it', _('Italiano')),
('ja', _('日本語')),
('nl', _('Nederlands')),
('pt', _('Português')),
('ru', _('Русский')),
('sq', _('Shqip')),
('tr', _('Türkçe')),
('uk', _('Українська')),
)
MESSAGE_TAGS = {
message_constants.DEBUG: 'debug',
message_constants.INFO: 'info',
message_constants.SUCCESS: 'success',
message_constants.WARNING: 'warning',
message_constants.ERROR: 'danger',
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': [
'127.0.0.1:11211',
],
}
}
'''
Local config
'''
from ivatar.settings import TEMPLATES # noqa
SESSION_COOKIE_SECURE = False
DEBUG = True
TEMPLATES[0]['OPTIONS']['debug'] = True
......@@ -90,16 +90,14 @@ class UploadPhotoForm(forms.Form):
required=True,
error_messages={
'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(
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.')
_('This field must be checked since we need to be able to distribute photos to third parties.')
})
@staticmethod
......@@ -191,8 +189,7 @@ class UploadLibravatarExportForm(forms.Form):
required=True,
error_messages={
'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(
label=_('can be freely copied'),
......@@ -202,3 +199,6 @@ class UploadLibravatarExportForm(forms.Form):
_('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())
# Generated by Django 2.1.5 on 2019-02-18 16:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ivataraccount', '0013_auto_20181203_1421'),
]
operations = [
migrations.AlterModelOptions(
name='unconfirmedemail',
options={'verbose_name': 'unconfirmed email', 'verbose_name_plural': 'unconfirmed emails'},
),
]
......@@ -37,7 +37,7 @@ from .gravatar import get_photo as get_gravatar_photo
def file_format(image_type):
'''
Helper method returning a 3 character long image type
Helper method returning a short image type
'''
if image_type == 'JPEG':
return 'jpg'
......@@ -52,7 +52,7 @@ def pil_format(image_type):
'''
Helper method returning the 'encoder name' for PIL
'''
if image_type == 'jpg':
if image_type == 'jpg' or image_type == 'jpeg':
return 'JPEG'
elif image_type == 'png':
return 'PNG'
......@@ -221,8 +221,9 @@ class Photo(BaseAccountModel):
if dimensions['a'] > MAX_PIXELS or dimensions['b'] > MAX_PIXELS:
messages.error(
request,
_('Image dimensions are too big(max: %s x %s' %
(MAX_PIXELS, MAX_PIXELS)))
_('Image dimensions are too big (max: %(max_pixels)s x %(max_pixels)s' % {
max_pixels: MAX_PIXELS,
}))
return HttpResponseRedirect(reverse_lazy('profile'))
if dimensions['w'] == 0 and dimensions['h'] == 0:
......@@ -350,8 +351,8 @@ class UnconfirmedEmail(BaseAccountModel):
'''
Class attributes
'''
verbose_name = _('unconfirmed_email')
verbose_name_plural = _('unconfirmed_emails')
verbose_name = _('unconfirmed email')
verbose_name_plural = _('unconfirmed emails')
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
......
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% block title %}{% trans 'Delete your Libravatar account' %}{% endblock title %}
{% block content %}
<h1>{% trans 'Delete your account' %}</h1>
<p><strong>{% trans 'There is no way to undo this operation.' %}</strong></p>
<form method="post" name="deleteaccount" id="form-deleteaccount">{% csrf_token %}
{% if user.password %}
<p>{% trans 'Please confirm your identity by entering your current password.' %}</p>
{{ form.password.errors }}
<div class="form-group" style='max-width:300px;'>
<label for="id_password">{% trans 'Password' %}:</label>
<input type="password" name="password" autofocus required class="form-control" id="id_password">
</div>
{% endif %}
<p>{% trans 'Are you sure you want to <strong>permanently delete</strong> your Libravatar account?' %}</p>
<button type="submit" class="btn btn-danger">{% trans 'Yes, delete all of my stuff' %}</button>
&nbsp;
<button type="cancel" class="btn btn-default" href="{% url 'profile' %}">{% trans 'Cancel' %}</button>
</form>
<div style="height:40px"></div>
{% endblock content %}
......@@ -7,22 +7,47 @@
{% block content %}
<h1>{% trans 'Account settings' %}</h1>
<h2>{% trans 'Theme' %}</h2>
<p>
<form method="post" action="{% url 'user_preference' %}">{% csrf_token %}
<div class="form-group">
{% for theme in THEMES %}
<div class="radio">
<input type="radio" name="theme" value="{{ theme.0 }}" id="theme-{{ theme.0 }}" {% if user.userpreference.theme == theme.0 %}checked{% endif %}>
<label for="theme-{{ theme.0 }}">{{ theme.1 }}</label>
</div>
{% endfor %}
<br/>
<button type="submit" class="btn btn-default">{% trans 'Save' %}</button>
<form method="post" action="{% url 'user_preference' %}">{% csrf_token %}
<div class="form-group">
{% for theme in THEMES %}
<div class="radio">
<input type="radio" name="theme" value="{{ theme.0 }}"
id="theme-{{ theme.0 }}"
{% if user.userpreference.theme == theme.0 %}checked{% endif %}
{% if THEMES|first == theme %}{% if not user.userpreference.theme %}checked{% endif %}{% endif %}
>
<label for="theme-{{ theme.0 }}">{{ theme.1 }}</label>
</div>
</form>
{% endfor %}
<br/>
<button type="submit" class="btn btn-default">{% trans 'Save' %}</button>
</div>
</form>
</p>
<!-- TODO: Language stuff not yet fully implemented; Esp. translations are only half-way there
<h2>{% trans 'Language' %}</h2>
<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
<div class="form-group">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<div class="radio">
<input type="radio" name="language" value="{{ language.code }}" id="language-{{ language.code }}" {% if language.code == LANGUAGE_CODE %}checked{% endif %}>
<label for="language-{{ language.code }}">{{ language.name_local }}</label>
</div>
{% endfor %}
</div>
<br/>
<button type="submit" class="btn btn-default">{% trans 'Save' %}</button>
</form>
-->
<div style="height:40px"></div>
<!-- <p><a href="{% url 'export' %}" class="btn btn-default">{% trans 'Export your data' %}</a></p> -->
......
......@@ -40,6 +40,8 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
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')
first_name = random_string()
last_name = random_string()
def login(self):
'''
......@@ -55,6 +57,8 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
self.user = User.objects.create_user(
username=self.username,
password=self.password,
first_name=self.first_name,
last_name=self.last_name,
)
def test_new_user(self):
......@@ -582,11 +586,12 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
def test_upload_gif_image(self):
'''
Test if gif is correctly detected
Test if gif is correctly detected and can be viewed
'''
self.login()
url = reverse('upload_photo')
# rb => Read binary
# Broken is _not_ broken - it's just an 'x' :-)
with open(os.path.join(settings.STATIC_ROOT, 'img', 'broken.gif'),
'rb') as photo:
response = self.client.post(url, {
......@@ -597,10 +602,59 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
self.assertEqual(
str(list(response.context[0]['messages'])[0]),
'Successfully uploaded',
'Invalid image data should return error message!')
'GIF upload failed?!')
self.assertEqual(
self.user.photo_set.first().format, 'gif',
'Format must be gif, since we uploaded a GIF!')
self.test_confirm_email()
self.user.confirmedemail_set.first().photo = self.user.photo_set.first()
urlobj = urlsplit(
libravatar_url(
email=self.user.confirmedemail_set.first().email,
)
)
url = '%s?%s' % (urlobj.path, urlobj.query)
response = self.client.get(url, follow=True)
self.assertEqual(
response.status_code,
200,
'unable to fetch avatar?')
def test_upload_jpg_image(self):
'''
Test if jpg is correctly detected and can be viewed
'''
self.login()
url = reverse('upload_photo')
# rb => Read binary
# Broken is _not_ broken - it's just an 'x' :-)
with open(os.path.join(settings.STATIC_ROOT, 'img', 'broken.jpg'),
'rb') as photo:
response = self.client.post(url, {
'photo': photo,
'not_porn': True,
'can_distribute': True,
}, follow=True)
self.assertEqual(
str(list(response.context[0]['messages'])[0]),
'Successfully uploaded',
'JPEG upload failed?!')
self.assertEqual(
self.user.photo_set.first().format, 'jpg',
'Format must be jpeg, since we uploaded a jpeg!')
self.test_confirm_email()
self.user.confirmedemail_set.first().photo = self.user.photo_set.first()
urlobj = urlsplit(
libravatar_url(
email=self.user.confirmedemail_set.first().email,
)
)
url = '%s?%s' % (urlobj.path, urlobj.query)
response = self.client.get(url, follow=True)
self.assertEqual(
response.status_code,
200,
'unable to fetch avatar?')
def test_upload_unsupported_tif_image(self): # pylint: disable=invalid-name
'''
......@@ -654,6 +708,22 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
self.user.confirmedemail_set.first().photo,
self.user.photo_set.first())
def test_no_photo_to_email(self):
'''
Test assigning photo to mail address
'''
self.test_confirm_email()
url = reverse(
'assign_photo_email',
args=[self.user.confirmedemail_set.first().id])
response = self.client.post(url, {
'photoNone': True,
}, follow=True)
self.assertEqual(response.status_code, 200, 'cannot un-assign photo?')
self.assertEqual(
self.user.confirmedemail_set.first().photo,
None)
def test_assign_photo_to_email_wo_photo_for_testing_template(self): # pylint: disable=invalid-name
'''
Test assign photo template
......@@ -1158,7 +1228,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
response = self.client.get(url, follow=True)
self.assertRedirects(
response=response,
expected_url='/static/img/nobody/80.png',
expected_url='/static/img/mm/80.png',
msg_prefix='Why does this not redirect to the default img?')
# Eventually one should check if the data is the same
......@@ -1232,8 +1302,8 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
response = self.client.get(url, follow=True)
self.assertRedirects(
response=response,
expected_url='/static/img/nobody/80.png',
msg_prefix='Why does this not redirect to the default img?')
expected_url='/static/img/nobody.png',
msg_prefix='Why does this not redirect to nobody img?')
def test_avatar_url_default_gravatarproxy_disabled(self): # pylint: disable=invalid-name
'''
......@@ -1269,7 +1339,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
response = self.client.get(url, follow=False)
self.assertRedirects(
response=response,
expected_url='/gravatarproxy/fb7a6d7f11365642d44ba66dc57df56f?s=80',
expected_url='/gravatarproxy/fb7a6d7f11365642d44ba66dc57df56f?s=80&default=http://host.tld/img.png',
fetch_redirect_response=False,
msg_prefix='Why does this not redirect to the default img?')
......@@ -1313,3 +1383,109 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
self.test_avatar_url_mail(do_upload_and_confirm=False, size=(20, 20))
img = Image.open(BytesIO(self.user.photo_set.first().data))
self.assertEqual(img.size, (20, 20), 'cropped to 20x20, but resulting image isn\'t 20x20!?')
def test_password_change_view(self):
'''
Test password change view
'''
self.login()
url = reverse('password_change')
response = self.client.get(url)
self.assertEqual(
response.status_code,
200,
'unable to view password change view?')
def test_password_change_view_post_wrong_old_pw(self):
'''
Test password change view post
'''
self.login()
response = self.client.post(
reverse('password_change'), {
'old_password': 'xxx',
'new_password1': self.password,
'new_password2': self.password,
},
follow=True,
)
self.assertContains(
response,
'Your old password was entered incorrectly. Please enter it again.',
1,
200,
'Old password as entered incorrectly, site should raise an error'
)
def test_password_change_view_post_wrong_new_password1(self):
'''
Test password change view post
'''
self.login()
response = self.client.post(
reverse('password_change'), {
'old_password': self.password,
'new_password1': self.password + '.',
'new_password2': self.password,
},
follow=True,
)
self.assertContains(
response,
'The two password fields didn&#39;t match.',
1,
200,
'Old password as entered incorrectly, site should raise an error'
)
def test_password_change_view_post_wrong_new_password2(self):
'''
Test password change view post
'''
self.login()
response = self.client.post(
reverse('password_change'), {
'old_password': self.password,
'new_password1': self.password,
'new_password2': self.password + '.',
},
follow=True,
)
self.assertContains(
response,
'The two password fields didn&#39;t match.',
1,
200,
'Old password as entered incorrectly, site should raise an error'
)
def test_profile_must_list_first_and_lastname(self):
'''
Test if profile view correctly lists first -/last name
'''
self.login()
response = self.client.get(reverse('profile'))
self.assertContains(
response,
self.first_name,
1,
200,
'First name not listed in profile page',
)
self.assertContains(
response,
self.last_name,
1,
200,
'Last name not listed in profile page',
)
self.assertContains(
response,
self.first_name + ' ' + self.last_name,
1,
200,
'First and last name not correctly listed in profile page',
)
......@@ -23,6 +23,7 @@ from . views import CropPhotoView
from . views import UserPreferenceView, UploadLibravatarExportView
from . views import ResendConfirmationMailView
from . views import IvatarLoginView
from . views import DeleteAccountView
# Define URL patterns, self documenting
# To see the fancy, colorful evaluation of these use:
......@@ -60,9 +61,7 @@ urlpatterns = [ # pylint: disable=invalid-name
path('export/', login_required(
TemplateView.as_view(template_name='export.html')
), name='export'),
path('delete/', login_required(
TemplateView.as_view(template_name='delete.html')
), name='delete'),
path('delete/', DeleteAccountView.as_view(), name='delete'),
path('profile/', ProfileView.as_view(), name='profile'),
path('add_email/', AddEmailView.as_view(), name='add_email'),
path('add_openid/', AddOpenIDView.as_view(), name='add_openid'),
......
......@@ -39,6 +39,7 @@ from .gravatar import get_photo as get_gravatar_photo
from .forms import AddEmailForm, UploadPhotoForm, AddOpenIDForm
from .forms import UpdatePreferenceForm, UploadLibravatarExportForm
from .forms import DeleteAccountForm
from .models import UnconfirmedEmail, ConfirmedEmail, Photo
from .models import UnconfirmedOpenId, ConfirmedOpenId, DjangoOpenIDStore
from .models import UserPreference
......@@ -145,7 +146,6 @@ class RemoveUnconfirmedEmailView(SuccessMessageMixin, View):
return HttpResponseRedirect(reverse_lazy('profile'))
@method_decorator(login_required, name='dispatch')
class ConfirmEmailView(SuccessMessageMixin, TemplateView):
'''
View class for confirming an unconfirmed email address
......@@ -577,8 +577,11 @@ class RedirectOpenIDView(View):
messages.error(request, _('OpenID discovery failed: %s' % exc))
return HttpResponseRedirect(reverse_lazy('profile'))
except UnicodeDecodeError as exc: # pragma: no cover
msg = _('OpenID discovery failed (userid=%s) for %s: %s' %
(request.user.id, user_url.encode('utf-8'), exc))
msg = _('OpenID discovery failed (userid=%(userid)s) for %(userurl)s: %(message)s' % {
userid: request.user.id,
userurl: user_url.encode('utf-8'),
message: exc,
})
print("message: %s" % msg)
messages.error(request, msg)
......@@ -911,7 +914,41 @@ class PasswordResetView(PasswordResetViewOriginal):
try:
confirmed_email = ConfirmedEmail.objects.get(email=request.POST['email'])
confirmed_email.user.email = confirmed_email.email
if not confirmed_email.user.password:
random_pass = User.objects.make_random_password()
confirmed_email.user.set_pasword(random_pass)
confirmed_email.user.save()
except Exception as exc:
pass
return super().post(self, request, args, kwargs)
@method_decorator(login_required, name='dispatch')
class DeleteAccountView(SuccessMessageMixin, FormView):
'''
View class for account deletion
'''
template_name = 'delete.html'
form_class = DeleteAccountForm
success_url = reverse_lazy('home')
def get(self, request, *args, **kwargs):
return super().get(self, request, args, kwargs)
def post(self, request, *args, **kwargs):
'''
Handle account deletion
'''
if request.user.password:
if 'password' in request.POST:
if not request.user.check_password(request.POST['password']):
messages.error(request, _('Incorrect password'))
return HttpResponseRedirect(reverse_lazy('delete'))
else:
messages.error(request, _('No password given'))
return HttpResponseRedirect(reverse_lazy('delete'))
raise(_('No password given'))
request.user.delete() # should delete all confirmed/unconfirmed/photo objects
return super().post(self, request, args, kwargs)
body{font-family:'Source Sans Pro',Helvetica,Arial,sans-serif;color:#525252}.btn{border-bottom-width:3px;box-sizing:border-box;font-family:'Montserrat',sans-serif;text-transform:uppercase;background:#ff8800;overflow:hidden;position:relative;-webkit-transition:all .3s;-moz-transition:all .3s;-ms-transition:all .3s;transition:all .3s}.btn.btn-default{color:#ffa033;border-color:#ffa033;background:none}.btn.btn-primary{border-color:#b35f00}.btn:hover,.btn:active,.btn:focus{background:none;border-color:#cc6d00;color:#cc6d00}.btn:hover:after,.btn:active:after,.btn:focus:after{top:50%}.btn:after{content:'';position:absolute;z-index:-1;width:150%;height:200%;top:-190%;left:50%;background:#ffb866;-webkit-transform:translateX(-50%) translateY(-50%) skew(0, 5deg);-moz-transform:translateX(-50%) translateY(-50%) skew(0, 5deg);-ms-transform:translateX(-50%) translateY(-50%) skew(0, 5deg);transform:translateX(-50%) translateY(-50%) skew(0, 5deg);-webkit-transition:all .5s ease-out;-moz-transition:all .5s ease-out;-ms-transition:all .5s ease-out;transition:all .5s ease-out}.btn.btn-block:after{height:250%;width:200%;-webkit-transform:translateX(-50%) translateY(-50%) skew(0, 2deg);-moz-transform:translateX(-50%) translateY(-50%) skew(0, 2deg);-ms-transform:translateX(-50%) translateY(-50%) skew(0, 2deg);transform:translateX(-50%) translateY(-50%) skew(0, 2deg)}.hero{background-color:#ff8800;color:#fff;padding:90px 0 40px}.hero h1{font-weight:600;font-size:6em;color:rgba(255,255,255,0.5)}.hero h2{font-weight:200;font-size:30px;margin-bottom:30px}.hero small{color:rgba(0,0,0,0.4)}.hero .btn{display:inline-block}.hero .btn.btn-default{color:#ffbc70;border-color:#ffbc70;background:none}.hero .btn.btn-primary{border-color:#fff}.hero .btn:hover,.hero .btn:active,.hero .btn:focus{border-color:#fff;color:#995200}.hero .btn:after{background:rgba(255,255,255,0.5)}.hero .container{position:relative;z-index:10}.social{background-color:#ff8800;padding:30px 0 140px}.social ul{list-style:none;padding:0;margin:0}.social ul li{float:left;margin-right:15px;width:100px}.clipper,.clipper-footer{background-color:#fff;height:110px;width:100%;position:relative;top:-40px;-webkit-transform:skew(0, 2deg);-moz-transform:skew(0, 2deg);-ms-transform:skew(0, 2deg);transform:skew(0, 2deg);pointer-events:none;z-index:1}.clipper-footer{top:0}section.content{position:relative;top:-100px;margin-bottom:-100px;z-index:10}section.content h1,section.content h2,section.content h3,section.content h4,section.content h5,section.content h6{color:#cc6d00}section.content h2{font-weight:200;font-size:40px}section.content section{margin-bottom:20px;margin-top:20px}section.content .container>hr{-webkit-transform:skew(0, 2deg);-moz-transform:skew(0, 2deg);-ms-transform:skew(0, 2deg);transform:skew(0, 2deg);margin-top:80px;margin-bottom:40px}footer{background-color:#dddddd;color:#888888;padding:100px 0 40px;margin-top:-40px}footer .pull-left{margin-right:20px}footer .logo{float:left;display:inline-block;margin-right:5px;margin-top:-8px}footer .logo .circle{stroke:#888888;stroke-width:7;fill:none}footer .logo .polygon{fill:#888888}@media (max-width:768px){.hero{padding:50px 0 30px}.hero h1{font-size:4em}.social{padding:30px 0 100px}.btn{margin-bottom:5px}section.content section{margin-bottom:50px}}.color{display:inline-block;border-radius:50%;height:20px;width:20px}.color.blue{background-color:#36b7d7}.color.green{background-color:#3aa850}.color.red{background-color:#f7645e}.color.black{background-color:#525252}.navbar-tortin{border:0;background-color:#ff8800;color:#FFFFFF;border-radius:0}.form-control{border-bottom-width:3px;box-sizing:border-box;font-family:'Montserrat',sans-serif;overflow:hidden;position:relative;-webkit-transition:all .3s;-moz-transition:all .3s;-ms-transition:all .3s;transition:all .3s;border-color:#ffa033;background:none}.form-control:focus{border-color:#cc6d00;box-shadow:none}.navbar-tortin .navbar-brand,.navbar-tortin .navbar-text,.navbar-tortin .navbar-nav>li>a,.navbar-tortin .navbar-link,.navbar-tortin .btn-link{color:#FFFFFF}.navbar-tortin .navbar-nav>.active>a,.navbar-tortin .navbar-nav>.active>a:focus,.navbar-tortin .navbar-nav>.active>a:hover,.navbar-tortin .navbar-nav>li>a:focus,.navbar-tortin .navbar-nav>li>a:hover,.navbar-tortin .navbar-link:hover,.navbar-tortin .btn-link:focus,.navbar-tortin .btn-link:hover,.navbar-tortin .navbar-nav>.open>a,.navbar-tortin .navbar-nav>.open>a:focus,.navbar-tortin .navbar-nav>.open>a:hover{background-color:#cc6d00}.navbar-tortin .navbar-toggle{border-color:#FFFFFF}.navbar-tortin .navbar-toggle:hover{background-color:#FFFFFF}.navbar-tortin .navbar-toggle .icon-bar{background-color:#FFFFFF}.navbar-tortin .navbar-toggle:hover .icon-bar{background-color:#ff8800}.navbar-tortin .navbar-collapse,.navbar-tortin .navbar-form{border:0}.dropdown-menu{background-color:#ff8800;border:1px solid #cc6d00}.dropdown-menu>li>a{color:#FFFFFF}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#cc6d00;color:#FFFFFF}.checkbox input,.radio input{display:none}.checkbox input+label,.radio input+label{padding-left:0}.checkbox input+label:before,.radio input+label:before{font-family:FontAwesome;display:inline-block;letter-spacing:5px;font-size:20px;color:#ff8800;vertical-align:middle}.checkbox input+label:before{content:"\f0c8"}.checkbox input:checked+label:before{content:"\f14a"}.radio input+label:before{content:"\f10c"}.radio input:checked+label:before{content:"\f192"}.uploadbtn:before{position:absolute;left:0;right:0;text-align:center;content:"Select file";font-family:'Montserrat',sans-serif}.jcrop-holder>div>div:nth-child(1){outline-width:2px;outline-style:solid;outline-color:#ff8800}@media (max-width:767px){.navbar-tortin .navbar-nav .open .dropdown-menu>li>a{color:#FFFFFF}.navbar-tortin .navbar-nav .open .dropdown-menu>li>a:hover{background-color:#cc6d00}.navbar-tortin .navbar-nav .open .dropdown-menu>.active>a,.navbar-tortin .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-tortin .navbar-nav .open .dropdown-menu>.active>a:hover{background-color:#cc6d00}}.panel-tortin{border-color:#ff8800;border-bottom-width:3px}.panel-tortin>.panel-heading{color:#fff;background-color:#ff8800;border-color:#ff8800;font-family:'Montserrat',sans-serif}.panel-tortin>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ff8800}.panel-tortin>.panel-heading .badge{color:#ff8800;background-color:#fff}.panel-tortin>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ff8800}.alert.alert-danger{background-color:#FFFFFF;color:#f7645e;border-color:#f7645e;border-bottom-width:3px;box-sizing:border-box;font-family:'Montserrat',sans-serif;overflow:hidden;position:relative}.input-group-addon{border-bottom-width:3px;box-sizing:border-box;font-family:'Montserrat',sans-serif;overflow:hidden;position:relative;border-color:#ffa033;background:none;width:auto;height:36px}.radio{color:#ff8800}input[type="radio"]:checked+label{font-weight:bold}.btn{border-radius:0 !important}
@import 'tortin.less';
@bg-hero:#ff8800;
.btn {
border-radius: 0px !important;
}
'''
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'
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))
# Dunno why random tld doesn't work, but I'm too lazy now to investigate
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,
)
def test_contact_page(self):
"""
Test contact page
"""
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?')
def test_security_page(self):
"""
Test security page
"""
response = self.client.get(reverse('security'))
self.assertEqual(response.status_code, 200, 'no 200 ok?')
'''
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'
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))
# Dunno why random tld doesn't work, but I'm too lazy now to investigate
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,
)
def test_incorrect_digest(self):
"""
Test incorrect digest
"""
response = self.client.get('/avatar/%s' % 'x'*65)
self.assertEqual(response.status_code, 200, 'no 200 ok?')
......@@ -4,6 +4,7 @@ Classes for our ivatar.tools.forms
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from django.forms.utils import ErrorList
from ivatar.settings import AVATAR_MAX_SIZE
from ivatar.settings import MIN_LENGTH_URL, MAX_LENGTH_URL
......@@ -14,14 +15,12 @@ class CheckDomainForm(forms.Form):
'''
Form handling domain check
'''
can_distribute = forms.TextInput(
attrs={
'label': _('Domain'),
'required': True,
'error_messages': {
'required':
_('Cannot check without a domain name.')
}
domain = forms.CharField(
label=_('Domain'),
required=True,
error_messages={
'required':
_('Cannot check without a domain name.')
}
)
......@@ -58,8 +57,26 @@ class CheckForm(forms.Form):
required=True,
)
default_opt = forms.ChoiceField(
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')),
('none', _('None')),
],
)
default_url = forms.URLField(
label=_('Default URL'),
min_length=1,
max_length=MAX_LENGTH_URL,
required=False,
)
......@@ -67,6 +84,17 @@ class CheckForm(forms.Form):
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()
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'))
return self.cleaned_data
......@@ -6,27 +6,7 @@
{% block content %}
<h1>{% trans 'Check e-mail or openid' %}</h1>
<div style="max-width:640px">
<form method="post" name="check">
{% csrf_token %}
<div class="form-group"><label for="id_mail">{% trans 'E-Mail' %}</label>
<input type="email" name="mail" maxlength="254" minlength="6" class="form-control" placeholder="{% trans 'E-Mail' %}" {% if mailurl %} value="{{ form.mail.value }}" {% endif %} id="id_mail"></div>
<div class="form-group"><label for="id_openid">{% trans 'OpenID' %}</label>
<input type="text" name="openid" maxlength="255" minlength="11" class="form-control" placeholder="{% trans 'OpenID' %}" {% if openidurl %} value="{{ form.openid.value }}" {% endif %} id="id_openid"></div>
<div class="form-group"><label for="id_size">{% trans 'Size' %}</label>
<input type="number" name="size" min="5" max="512" class="form-control" placeholder="{% trans 'Size' %}" {% if mailurl or openidurl %} value="{{ form.size.value }}" {% else %} value="100" {% endif %} required id="id_size"></div>
<div class="form-group"><label for="id_default_url">{% trans 'Default URL' %}</label>
<input type="url" name="default_url" class="form-control" placeholder="{% trans 'Default URL' %}" {% if mailurl or openidurl %} value="{{ form.default_url.value }}" {% endif %} id="id_default_url"></div>
<div class="form-group">
<button type="submit" class="btn btn-default">{% trans 'Check' %}</button>
</div>
</form>
</div>
{% if mailurl or openidurl %}
<hr/>
<h2>This is what the avatars will look like depending on the hash and protocol you use:</h2>
<p>
{% if mail_hash %}
......@@ -41,7 +21,7 @@
<div class="row">
{% if mailurl %}
<div class="panel panel-tortin" style="min-width:132px;width:calc({{ size }}px + 32px);float:left;margin-left:30px">
<div class="panel panel-tortin" style="min-width:132px;width:calc({{ size }}px + 33px);float:left;margin-left:20px">
<div class="panel-heading">
<h3 class="panel-title">MD5 <i class="fa fa-lock" title="Secure connection (https)"></i>&nbsp;<i class="fa fa-at" title="mail: {{ form.mail.value }}"></i></h3>
</div>
......@@ -56,15 +36,15 @@
<h3 class="panel-title">SHA256 <i class="fa fa-lock" title="Secure connection (https)"></i>&nbsp;<i class="fa fa-at" title="mail: {{ form.mail.value }}"></i></h3>
</div>
<div class="panel-body">
<a href="{{ SECURE_BASE_URL }}{{ mail_hash256 }}?s={{ size }}">
<center><img src="{{ SECURE_BASE_URL }}{{ mail_hash256 }}?s={{ size }}" style="max-width: {{ size }}px; max-height: {{ size }}px;"></center>
<a href="{{ mailurl_secure_256 }}">
<center><img src="{{ mailurl_secure_256 }}" style="max-width: {{ size }}px; max-height: {{ size }}px;"></center>
</a>
</div>
</div>
{% endif %}
{% if openidurl %}
<div class="panel panel-tortin" style="min-width:122px;width:calc({{ size }}px + 33px);float:left;margin-left:30px">
<div class="panel panel-tortin" style="min-width:132px;width:calc({{ size }}px + 33px);float:left;margin-left:20px">
<div class="panel-heading">
<h3 class="panel-title">SHA256 <i class="fa fa-lock" title="Secure connection (http)"></i>&nbsp;<i class="fa fa-openid" title="openid: {{ form.openid.value }}"></i></h3>
</div>
......@@ -77,5 +57,50 @@
{% endif %}
</div>
{% endif %}
<h1>{% trans 'Check e-mail or openid' %}</h1>
{% if form.errors %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger" role="alert">{{ error|escape }}</div>
{% endfor %}
{% endif %}
<div style="max-width:640px">
<form method="post" name="check">
{% csrf_token %}
<div class="form-group"><label for="id_mail">{% trans 'E-Mail' %}</label>
<input type="email" name="mail" maxlength="254" minlength="6" class="form-control" placeholder="{% trans 'E-Mail' %}" {% if form.mail.value %} value="{{ form.mail.value }}" {% endif %} id="id_mail"></div>
<div class="form-group"><label for="id_openid">{% trans 'OpenID' %}</label>
<input type="text" name="openid" maxlength="255" minlength="11" class="form-control" placeholder="{% trans 'OpenID' %}" {% if form.openid.value %} value="{{ form.openid.value }}" {% endif %} id="id_openid"></div>
<div class="form-group"><label for="id_size">{% trans 'Size' %}</label>
<input type="number" name="size" min="5" max="512" class="form-control" placeholder="{% trans 'Size' %}" {% if form.size.value %} value="{{ form.size.value }}" {% else %} value="100" {% endif %} required id="id_size"></div>
{% if form.default_url.errors %}
<div class="alert alert-danger" role="alert">{{ form.default_url.errors }}</div>
{% endif %}
<div class="form-group"><label for="id_default_url">{% trans 'Default URL or special keyword' %}</label>
<input type="text" name="default_url" class="form-control" placeholder="{% trans 'Default' %}" {% if form.default_url.value %} value="{{ form.default_url.value }}" {% endif %} id="id_default_url"></div>
{% if form.default_opt.errors %}
<div class="alert alert-danger" role="alert">{{ form.default_opt.errors }}</div>
{% endif %}
<div class="form-group"><label for="id_default_opt">{% trans 'Default (special keyword)' %}</label>
{% for opt in form.default_opt.field.choices %}
<div class="radio" {% if forloop.counter|divisibleby:2 %}even{% else %}odd{% endif %}>
<input type="radio" name="default_opt" value="{{ opt.0 }}"
id="default_opt-{{ opt.0 }}"
{% if form.default_opt.value == opt.0 %}checked{% endif %}
>
<label for="default_opt-{{ opt.0 }}">{{ opt.1 }}</label>
</div>
{% endfor %}
</div>
<div class="form-group">
<button type="submit" class="btn btn-default">{% trans 'Check' %}</button>
</div>
</form>
</div>
<div style="height:40px"></div>
{% endblock content %}
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% block title %}{% trans 'Check domain' %}{% endblock title %}
{% block content %}
<div class="container">
<h1>{% trans 'Check domain' %}</h1>
{% if form.errors %}
<p class="error">{% trans "Please correct errors below:" %}<br>
{% if form.openid_identifier.errors %}
{{ form.openid_identifier.errors|join:', ' }}
{% endif %}
{% if form.next.errors %}
{{ form.next.errors|join:', ' }}
{% endif %}
</p>
{% endif %}
<div style="max-width:640px">
<form method="post" name="lookup">
{% csrf_token %}
<div class="form-group"><label for="id_domain">{% trans 'Domain' %}</label>
<input type="text" name="domain" maxlength="254" minlength="6" class="form-control" placeholder="{% trans 'Domain' %}" {% if form.domain.value %} value="{{ form.domain.value }}" {% endif %} id="id_domain">
</div>
<div class="form-group">
<button type="submit" class="btn btn-default">{% trans 'Check' %}</button>
</div>
</form>
</div>
<!-- TODO TODO TODO: I need better styling -->
{% if result %}
<hr/>
<h2>The following servers will be used for your domain</h2>
<div class="panel panel-tortin" style="width:intrinsic;margin-left:30px;float:left">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-unlock-alt"></i>&nbsp;HTTP Server</h3>
</div>
<div class="panel-body">
{% if result.avatar_server_http %}
<a href="{{result.avatar_server_http}}">
<h4>{{result.avatar_server_http}}</h4>
</a>
{% if result.avatar_server_http_ipv4 %}
<br><center>{{ result.avatar_server_http_ipv4 }}</center>
{% endif %}
{% if result.avatar_server_http_ipv6 %}
<br><center>{{ result.avatar_server_http_ipv6 }}</center>
{% endif %}
{% else %}
<a href="http://cdn.libravatar.org">
<h4>http://cdn.libravatar.org</h4>
</a>
{% endif %}
</div>
</div>
<div class="panel panel-tortin" style="width:intrinsic;margin-left:30px;float:left">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-lock"></i>&nbsp;HTTPS Server</h3>
</div>
<div class="panel-body">
{% if result.avatar_server_https %}
<a href="{{result.avatar_server_https}}">
<h4>{{result.avatar_server_https}}</h4>
</a>
{% if result.avatar_server_https_ipv4 %}
<br><center>{{ result.avatar_server_https_ipv4 }}</center>
{% endif %}
{% if result.avatar_server_https_ipv6 %}
<br><center>{{ result.avatar_server_https_ipv6 }}</center>
{% endif %}
{% else %}
<a href="https://seccdn.libravatar.org">
<h4>https://seccdn.libravatar.org</h4>
</a>
{% endif %}
</div>
</div>
{% endif %}
</div>
<div style="height:40px"></div>
{% endblock content %}
'''
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'
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))
# Dunno why random tld doesn't work, but I'm too lazy now to investigate
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,
)
def test_check(self):
"""
Test check page
"""
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?')
'''
View classes for ivatar/tools/
'''
from socket import inet_ntop, AF_INET6
from django.views.generic.edit import FormView
from django.urls import reverse_lazy as reverse
from django.shortcuts import render
import DNS
from libravatar import libravatar_url, parse_user_identity
from libravatar import SECURE_BASE_URL as LIBRAVATAR_SECURE_BASE_URL
from libravatar import BASE_URL as LIBRAVATAR_BASE_URL
......@@ -20,7 +24,24 @@ class CheckDomainView(FormView):
'''
template_name = 'check_domain.html'
form_class = CheckDomainForm
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,
})
class CheckView(FormView):
'''
......@@ -34,6 +55,7 @@ class CheckView(FormView):
mailurl = None
openidurl = None
mailurl_secure = None
mailurl_secure_256 = None
openidurl_secure = None
mail_hash = None
mail_hash256 = None
......@@ -44,18 +66,22 @@ class CheckView(FormView):
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']:
mailurl = libravatar_url(
email=form.cleaned_data['mail'],
size=form.cleaned_data['size'],
size=size,
default=default_url)
mailurl = mailurl.replace(LIBRAVATAR_BASE_URL, BASE_URL)
mailurl_secure = libravatar_url(
email=form.cleaned_data['mail'],
size=form.cleaned_data['size'],
size=size,
https=True,
default=default_url)
mailurl_secure = mailurl_secure.replace(
......@@ -67,18 +93,20 @@ class CheckView(FormView):
hash_obj = hashlib.new('sha256')
hash_obj.update(form.cleaned_data['mail'].encode('utf-8'))
mail_hash256 = hash_obj.hexdigest()
size = form.cleaned_data['size']
mailurl_secure_256 = mailurl_secure.replace(
mail_hash,
mail_hash256)
if form.cleaned_data['openid']:
if form.cleaned_data['openid'][-1] != '/':
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=form.cleaned_data['size'],
size=size,
default=default_url)
openidurl = openidurl.replace(LIBRAVATAR_BASE_URL, BASE_URL)
openidurl_secure = libravatar_url(
openid=form.cleaned_data['openid'],
size=form.cleaned_data['size'],
size=size,
https=True,
default=default_url)
openidurl_secure = openidurl_secure.replace(
......@@ -87,16 +115,153 @@ class CheckView(FormView):
openid_hash = parse_user_identity(
openid=form.cleaned_data['openid'],
email=None)[0]
size = form.cleaned_data['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]
service_name = None
if https:
service_name = "_avatars-sec._tcp.%s" % domain
else:
service_name = "_avatars._tcp.%s" % domain
DNS.DiscoverNameServers()
try:
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':
# 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))
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'):
continue
record = {'priority': int(answer['data'][0]), 'weight': int(answer['data'][1]),
'port': int(answer['data'][2]), 'target': answer['data'][3]}
records.append(record)
target, port = srv_hostname(records)
if target and ((https and port != 443) or (not https and port != 80)):
return "%s:%s" % (target, port)
return target
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'])
# Keep only the servers in the top priority
priority_records = []
total_weight = 0
top_priority = records[0]['priority'] # highest priority = lowest number
for ret in records:
if ret['priority'] > top_priority:
# ignore the record (ret has lower priority)
continue
elif ret['priority'] < top_priority:
# reset the aretay (ret has higher priority)
top_priority = ret['priority']
total_weight = 0
priority_records = []
total_weight += ret['weight']
if ret['weight'] > 0:
priority_records.append((total_weight, ret))
else:
# zero-weigth elements must come first
priority_records.insert(0, (0, ret))