First tool: Check OpenID/mail

This commit is contained in:
Oliver Falk
2018-07-03 11:56:14 +02:00
parent 78600c1670
commit cb99a0c678
10 changed files with 261 additions and 7 deletions

View File

@@ -25,6 +25,7 @@ INSTALLED_APPS.extend([
'anymail',
'ivatar',
'ivatar.ivataraccount',
'ivatar.tools',
])
from ivatar.settings import MIDDLEWARE # noqa
@@ -54,6 +55,9 @@ OPENID_UPDATE_DETAILS_FROM_SREG = True
SITE_NAME = 'ivatar'
IVATAR_VERSION = '0.1'
SECURE_BASE_URL = 'https://avatars.linux-kernel.at/avatar/'
BASE_URL = 'http://avatars.linux-kernel.at/avatar/'
LOGIN_REDIRECT_URL = reverse_lazy('profile')
MAX_LENGTH_EMAIL = 254 # http://stackoverflow.com/questions/386294
SERVER_EMAIL = 'accounts@mg.linux-kernel.at'
@@ -65,6 +69,13 @@ MAX_PIXELS = 7000
AVATAR_MAX_SIZE = 512
JPEG_QUALITY = 85
# I'm not 100% sure if single character domains are possible
# under any tld... so MIN_LENGTH_EMAIL/_URL, might be +1
MIN_LENGTH_URL = 11 # eg. http://a.io
MAX_LENGTH_URL = 255 # MySQL can't handle more than that (LP: 1018682)
MIN_LENGTH_EMAIL = 6 # eg. x@x.xx
MAX_LENGTH_EMAIL = 254 # http://stackoverflow.com/questions/386294
BOOTSTRAP4 = {
'include_jquery': False,
'javascript_in_head': False,

View File

@@ -12,7 +12,8 @@ from django.core.mail import send_mail
from ipware import get_client_ip
from ivatar import settings
from ivatar.settings import MAX_LENGTH_EMAIL
from ivatar.settings import MIN_LENGTH_EMAIL, MAX_LENGTH_EMAIL
from ivatar.settings import MIN_LENGTH_URL, MAX_LENGTH_URL
from ivatar.ivataraccount.models import MAX_LENGTH_URL
from . models import UnconfirmedEmail, ConfirmedEmail, Photo
from . models import UnconfirmedOpenId, ConfirmedOpenId
@@ -26,8 +27,8 @@ class AddEmailForm(forms.Form):
'''
email = forms.EmailField(
label=_('Email'),
min_length=MIN_LENGTH_EMAIL,
max_length=MAX_LENGTH_EMAIL,
min_length=6, # x@x.xx
)
def clean_email(self):
@@ -136,10 +137,8 @@ class AddOpenIDForm(forms.Form):
'''
openid = forms.URLField(
label=_('OpenID'),
min_length=MIN_LENGTH_URL,
max_length=MAX_LENGTH_URL,
# However, not 100% sure if single character domains are possible
# under any tld...
min_length=11, # eg. http://a.io
initial='http://'
)

View File

@@ -365,6 +365,9 @@ class ConfirmedOpenId(BaseAccountModel):
lowercase_url = urlunsplit(
(url.scheme.lower(), netloc, url.path, url.query, url.fragment)
)
if lowercase_url[-1] != '/':
lowercase_url += '/'
self.openid = lowercase_url
self.digest = hashlib.sha256(lowercase_url.encode('utf-8')).hexdigest()
return super().save(force_insert, force_update, using, update_fields)

View File

@@ -38,7 +38,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
password = random_string()
email = '%s@%s.%s' % (username, random_string(), random_string(2))
# Dunno why random tld doens'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):
'''
@@ -822,7 +822,6 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
},
)
self.assertEqual(response.status_code, 302, 'OpenID must redirect')
response = self.client.post(
reverse('add_openid'), {
'openid': self.openid,

72
ivatar/tools/forms.py Normal file
View File

@@ -0,0 +1,72 @@
'''
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 ivatar.settings import AVATAR_MAX_SIZE
from ivatar.settings import MIN_LENGTH_URL, MAX_LENGTH_URL
from ivatar.settings import MIN_LENGTH_EMAIL, MAX_LENGTH_EMAIL
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.')
}
}
)
class CheckForm(forms.Form):
'''
Form handling check
'''
mail = forms.EmailField(
label=_('E-Mail'),
required=False,
min_length=MIN_LENGTH_EMAIL,
max_length=MAX_LENGTH_EMAIL,
error_messages={
'required':
_('Cannot check without a domain name.')
})
openid = forms.CharField(
label=_('OpenID'),
required=False,
min_length=MIN_LENGTH_URL,
max_length=MAX_LENGTH_URL,
error_messages={
'required':
_('Cannot check without an openid name.')
})
size = forms.IntegerField(
label=_('Size'),
initial=80,
min_value=5,
max_value=AVATAR_MAX_SIZE,
required=True,
)
default_url = forms.URLField(
label=_('Default URL'),
required=False,
)
def clean(self):
self.cleaned_data = super().clean()
mail = self.cleaned_data.get('mail')
openid = self.cleaned_data.get('openid')
if not mail and not openid:
raise ValidationError(_('Either OpenID or mail must be specified'))
return self.cleaned_data

View File

@@ -0,0 +1,75 @@
{% extends 'base.html' %}
{% load i18n %}
{% load bootstrap4 %}
{% load static %}
{% block title %}{% trans 'Check e-mail or openid' %}{% endblock title %}
{% block content %}
<h1>{% trans 'Check e-mail or openid' %}</h1>
<div style="width:600px;">
<form method="post" name="check">{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">{% trans 'Check' %}</button>
<button type="cancel" class="btn btn-danger">{% trans 'Cancel' %}</button>
{% endbuttons %}
</form>
</div>
{% if mailurl or openidurl %}
<p>
This is what the avatars will look like depending on the hash and protocol you use:<br/>
{% if mail_hash %}
MD5 hash (mail): {{ mail_hash }}<br/>
SHA256 hash (mail): {{ mail_hash256 }}<br/>
{% endif %}
{% if openid_hash %}
SHA256 hash (OpenID): {{ openid_hash }}<br/>
{% endif %}
</p>
<ul class="horizontal-list avatar-list centered">
{% if mailurl %}
<li>
<img src="{{ mailurl }}" style="max-width: {{ size }}px; max-height: {{ size }}px;">
<br/>
MD5
</li>
<li>
<img src="{{ mailurl_secure }}" style="max-width: {{ size }}px; max-height: {{ size }}px;">
<br/>
MD5 <img src="{% static 'img/https_lock.png' %}">
</li>
{% endif %}
{% if openidurl %}
<li>
<img src="{{ openidurl }}" style="max-width: {{ size }}px; max-height: {{ size }}px;">
<br/>
SHA256
</li>
<li>
<img src="{{ openidurl_secure }}" style="max-width: {{ size }}px; max-height: {{ size }}px;">
<br/>
SHA256 <img src="{% static 'img/https_lock.png' %}">
</li>
{% endif %}
</ul>
{% endif %}
{# Bad hack in order to have the images for sure inside our "outer" div box #}
<!-- Bad hack -->
{% if mailurl %}
<div style="height: {{ size }}px;">&nbsp;</div>
{% endif %}
{% if openidurl %}
<div style="height: {{ size }}px;">&nbsp;</div>
{% endif %}
<!-- End of bad hack -->
{% endblock content %}

11
ivatar/tools/urls.py Normal file
View File

@@ -0,0 +1,11 @@
'''
ivatar/tools URL configuration
'''
from django.conf.urls import url
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'),
]

80
ivatar/tools/views.py Normal file
View File

@@ -0,0 +1,80 @@
'''
View classes for ivatar/tools/
'''
from django.views.generic.edit import FormView
from django.urls import reverse_lazy as reverse
from django.shortcuts import render
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
import hashlib
from .forms import CheckDomainForm, CheckForm
from ivatar.settings import SECURE_BASE_URL, BASE_URL
class CheckDomainView(FormView):
'''
View class for checking a domain
'''
template_name = 'check_domain.html'
form_class = CheckDomainForm
class CheckView(FormView):
'''
View class for checking an e-mail or openid address
'''
template_name = 'check.html'
form_class = CheckForm
success_url = reverse('tools_check')
def form_valid(self, form):
mailurl = None
openidurl = None
mailurl_secure = None
openidurl_secure = None
mail_hash = None
mail_hash256 = None
openid_hash = None
size = 80
super().form_valid(form)
if form.cleaned_data['default_url']:
default_url = form.cleaned_data['default_url']
else:
default_url = None
if form.cleaned_data['mail']:
mailurl = libravatar_url(email=form.cleaned_data['mail'], size=form.cleaned_data['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'], https=True, default=default_url)
mailurl_secure = mailurl_secure.replace(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'))
mail_hash256 = hash_obj.hexdigest()
size = form.cleaned_data['size']
if form.cleaned_data['openid']:
if form.cleaned_data['openid'][-1] != '/':
form.cleaned_data['openid'] += '/'
openidurl = libravatar_url(openid=form.cleaned_data['openid'], size=form.cleaned_data['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'], https=True, default=default_url)
openidurl_secure = openidurl_secure.replace(LIBRAVATAR_SECURE_BASE_URL, SECURE_BASE_URL)
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,
'openidurl_secure': openidurl_secure,
'mail_hash': mail_hash,
'mail_hash256': mail_hash256,
'openid_hash': openid_hash,
'size': size,
})

View File

@@ -13,6 +13,7 @@ urlpatterns = [ # pylint: disable=invalid-name
path('admin/', admin.site.urls),
url('openid/', include('django_openid_auth.urls')),
url('accounts/', include('ivatar.ivataraccount.urls')),
url('tools/', include('ivatar.tools.urls')),
url(
r'avatar/(?P<digest>\w{64})',
AvatarImageView.as_view(), name='avatar_view'),

View File

@@ -19,13 +19,16 @@
</div>
{% autoescape off %}{% bootstrap_messages %}{% endautoescape %}
{# TODO: Fix URLs!!! #}
<div id="account">
{% if user.is_authenticated %}
<a href="{% url 'profile' %}">{% trans 'Profile' %}</a>&nbsp;|&nbsp;
<a href="{% url 'tools_check' %}">{% trans 'Check' %}</a>&nbsp;|&nbsp;
<a href="{{ contact_us }}">{% trans 'Contact Us' %}</a>&nbsp;|&nbsp;
<a href="{{ security_url }}">{% trans 'Security' %}</a>&nbsp;|&nbsp;
<a href="{% url 'logout' %}" id="logout-link">{% trans 'Logout' %}</a>
{% else %}
<a href="{% url 'tools_check' %}">{% trans 'Check' %}</a>&nbsp;|&nbsp;
<a href="{{ contact_us }}">{% trans 'Contact Us' %}</a>&nbsp;|&nbsp;
<a href="{{ security_url }}">{% trans 'Security' %}</a>&nbsp;|&nbsp;
<a href="{% url 'login' %}">{% trans 'Login' %}</a>