Commit 635951ff authored by Oliver Falk's avatar Oliver Falk

Make pylint happier, enhance a few tests and add missing schemas

parent 8fb601ec
This diff is collapsed.
......@@ -108,7 +108,7 @@ else:
'MAILGUN_SENDER_DOMAIN': os.environ['IVATAR_MAILGUN_SENDER_DOMAIN'],
}
EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend' # pragma: no cover
except Exception as exc:
except Exception as exc: # pragma: nocover
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
SERVER_EMAIL = os.environ.get('SERVER_EMAIL', 'ivatar@mg.linux-kernel.at')
......
......@@ -62,12 +62,14 @@ class AddEmailForm(forms.Form):
_('Address already added, currently unconfirmed'))
return False
# Check whether or not the email is already confirmed by someone
if ConfirmedEmail.objects.filter(
email=self.cleaned_data['email']).exists():
self.add_error(
'email',
_('Address already confirmed (by someone else)'))
# Check whether or not the email is already confirmed (by someone)
check_mail = ConfirmedEmail.objects.filter(
email=self.cleaned_data['email'])
if check_mail.exists():
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)
return False
unconfirmed = UnconfirmedEmail()
......
......@@ -25,7 +25,7 @@ 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.ivataraccount.models import Photo, ConfirmedOpenId, ConfirmedEmail
from ivatar.utils import random_string
# pylint: enable=wrong-import-position
......@@ -452,7 +452,7 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
'email',
'Address already added, currently unconfirmed')
def test_add_already_confirmed_email(self): # pylint: disable=invalid-name
def test_add_already_confirmed_email_self(self): # pylint: disable=invalid-name
'''
Request adding mail address that is already confirmed (by someone)
'''
......@@ -460,6 +460,33 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
# Should set EMAIL_BACKEND, so no need to do it here
self.test_confirm_email()
response = self.client.post(
reverse('add_email'), {
'email': self.email,
},
follow=True,
)
self.assertFormError(
response,
'form',
'email',
'Address already confirmed (by you)')
def test_add_already_confirmed_email_other(self): # pylint: disable=invalid-name
'''
Request adding mail address that is already confirmed (by someone)
'''
# Create test mail and confirm it, reuse test code
# Should set EMAIL_BACKEND, so no need to do it here
self.test_confirm_email()
# Create another user and assign the mail address to that one
# in order to test the correct error message
otheruser = User.objects.create(username='otheruser')
confirmedemail = ConfirmedEmail.objects.last()
confirmedemail.user = otheruser
confirmedemail.save()
response = self.client.post(
reverse('add_email'), {
'email': self.email,
......
......@@ -9,7 +9,7 @@ class MultipleProxyMiddleware(MiddlewareMixin): # pylint: disable=too-few-publi
with multiple proxies
"""
def process_request(self, request):
def process_request(self, request): # pylint: disable=no-self-use
"""
Rewrites the proxy headers so that forwarded server is
used if available.
......
......@@ -2,32 +2,18 @@
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
from ivatar.utils import random_string
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
'''
......@@ -77,4 +63,3 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"""
response = self.client.get(reverse('security'))
self.assertEqual(response.status_code, 200, 'no 200 ok?')
......@@ -2,32 +2,17 @@
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
from ivatar.utils import random_string
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
'''
......
......@@ -17,6 +17,6 @@ class TestCase(unittest.TestCase):
'''
Run wsgi import
'''
import ivatar.wsgi
import ivatar.wsgi # pylint: disable=import-outside-toplevel
self.assertEqual(ivatar.wsgi.application.__class__,
django.core.handlers.wsgi.WSGIHandler)
......@@ -2,6 +2,8 @@
View classes for ivatar/tools/
'''
from socket import inet_ntop, AF_INET6
import hashlib
import random
from django.views.generic.edit import FormView
from django.urls import reverse_lazy as reverse
......@@ -12,10 +14,9 @@ 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
import hashlib
from .forms import CheckDomainForm, CheckForm
from ivatar.settings import SECURE_BASE_URL, BASE_URL
from .forms import CheckDomainForm, CheckForm # pylint: disable=relative-beyond-top-level
class CheckDomainView(FormView):
......@@ -32,12 +33,20 @@ class CheckDomainView(FormView):
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_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)
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,
......@@ -75,21 +84,21 @@ class CheckView(FormView):
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'],
size=size,
https=True,
default=default_url)
email=form.cleaned_data['mail'],
size=size,
https=True,
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]
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()
......@@ -97,24 +106,25 @@ class CheckView(FormView):
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://'):
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'],
size=size,
https=True,
default=default_url)
openid=form.cleaned_data['openid'],
size=size,
https=True,
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,
......@@ -166,7 +176,8 @@ def lookup_avatar_server(domain, https):
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]),
......@@ -203,7 +214,10 @@ def srv_hostname(records):
if ret['priority'] > top_priority:
# ignore the record (ret has lower priority)
continue
elif ret['priority'] < top_priority:
# 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:
# reset the aretay (ret has higher priority)
top_priority = ret['priority']
total_weight = 0
......@@ -218,7 +232,7 @@ def srv_hostname(records):
priority_records.insert(0, (0, ret))
if len(priority_records) == 1:
unused, ret = priority_records[0]
unused, ret = priority_records[0] # pylint: disable=unused-variable
return (ret['target'], ret['port'])
# Select first record according to RFC2782 weight ordering algorithm (page 3)
......@@ -261,7 +275,7 @@ def lookup_ip_address(hostname, ipv6):
if ipv6:
return inet_ntop(AF_INET6, answer['data'])
else:
return answer['data']
return answer['data']
return None
......@@ -29,7 +29,8 @@ urlpatterns = [ # pylint: disable=invalid-name
GravatarProxyView.as_view(), name='gravatarproxy'),
url('description/', TemplateView.as_view(template_name='description.html'), name='description'),
# The following two are TODO TODO TODO TODO TODO
url('run_your_own/', TemplateView.as_view(template_name='run_your_own.html'), name='run_your_own'),
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'),
......@@ -39,10 +40,10 @@ urlpatterns = [ # pylint: disable=invalid-name
MAINTENANCE = False
try:
if settings.MAINTENANCE:
MAINTENANCE = True
except:
pass
if settings.MAINTENANCE:
MAINTENANCE = True
except: # pylint: disable=bare-except
pass
if MAINTENANCE:
urlpatterns.append(url('', TemplateView.as_view(template_name='maintenance.html'), name='home'))
......
......@@ -57,7 +57,10 @@ def get_size(request, size=DEFAULT_AVATAR_SIZE):
class CachingHttpResponse(HttpResponse):
def __init__(self, uri, content=b'', content_type=None, status=200, reason=None, charset=None):
'''
Handle caching of response
'''
def __init__(self, uri, content=b'', content_type=None, status=200, reason=None, charset=None): # pylint: disable=too-many-arguments
if CACHE_RESPONSE:
caches['filesystem'].set(uri, {
'content': content,
......@@ -66,7 +69,7 @@ class CachingHttpResponse(HttpResponse):
'reason': reason,
'charset': charset
})
return super().__init__(content, content_type, status, reason, charset)
super().__init__(content, content_type, status, reason, charset)
class AvatarImageView(TemplateView):
'''
......@@ -74,7 +77,7 @@ class AvatarImageView(TemplateView):
'''
# TODO: Do cache resize images!! Memcached?
def options(self, request, *args, **kwargs): # pylint: disable=too-many-branches,too-many-statements,too-many-locals,too-many-return-statements
def options(self, request, *args, **kwargs):
response = HttpResponse("", content_type='text/plain')
response['Allow'] = "404 mm mp retro pagan wavatar monsterid robohash identicon"
return response
......@@ -137,7 +140,7 @@ class AvatarImageView(TemplateView):
except ObjectDoesNotExist:
model = ConfirmedOpenId
try:
d = kwargs['digest']
d = kwargs['digest'] # pylint: disable=invalid-name
# OpenID is tricky. http vs. https, versus trailing slash or not
# However, some users eventually have added their variations already
# and therfore we need to use filter() and first()
......@@ -146,7 +149,7 @@ class AvatarImageView(TemplateView):
Q(alt_digest1=d) |
Q(alt_digest2=d) |
Q(alt_digest3=d)).first()
except:
except: # pylint: disable=bare-except
pass
......@@ -212,7 +215,7 @@ class AvatarImageView(TemplateView):
img = img.resize((size, size), Image.ANTIALIAS)
img.save(data, 'PNG', quality=JPEG_QUALITY)
data.seek(0)
response = CachingHttpResponse(
response = CachingHttpResponse(
uri,
data,
content_type='image/png')
......@@ -233,7 +236,7 @@ class AvatarImageView(TemplateView):
return response
if str(default) == 'identicon':
p = Pydenticon5()
p = Pydenticon5() # pylint: disable=invalid-name
# In order to make use of the whole 32 bytes digest, we need to redigest them.
newdigest = hashlib.md5(bytes(kwargs['digest'], 'utf-8')).hexdigest()
img = p.draw(newdigest, size, 0)
......@@ -306,7 +309,7 @@ class GravatarProxyView(View):
'''
# TODO: Do cache images!! Memcached?
def get(self, request, *args, **kwargs): # pylint: disable=too-many-branches,too-many-statements,too-many-locals,no-self-use,unused-argument
def get(self, request, *args, **kwargs): # pylint: disable=too-many-branches,too-many-statements,too-many-locals,no-self-use,unused-argument,too-many-return-statements
'''
Override get from parent class
'''
......@@ -314,7 +317,7 @@ class GravatarProxyView(View):
url = reverse_lazy(
'avatar_view',
args=[kwargs['digest']]) + '?s=%i' % size + '&forcedefault=y'
if default != None:
if default is not None:
url += '&default=%s' % default
return HttpResponseRedirect(url)
......@@ -325,7 +328,7 @@ class GravatarProxyView(View):
try:
if str(request.GET['default']) != 'None':
default = request.GET['default']
except:
except: # pylint: disable=bare-except
pass
if str(default) != 'wavatar':
......@@ -344,7 +347,7 @@ class GravatarProxyView(View):
if hashlib.md5(data.read()).hexdigest() == '71bc262d627971d13fe6f3180b93062a':
cache.set(gravatar_test_url, 'default', 60)
return redir_default(default)
except Exception as exc:
except Exception as exc: # pylint: disable=broad-except
print('Gravatar test url fetch failed: %s' % exc)
gravatar_url = 'https://secure.gravatar.com/avatar/' + kwargs['digest'] \
......
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="https://www.libravatar.org/schemas/export/0.1"
xmlns="https://www.libravatar.org/schemas/export/0.1"
elementFormDefault="qualified">
<xsd:annotation>
<xsd:documentation>
XML Schema for Libravatar user account exports.
Last Modifed 2011-04-09
</xsd:documentation>
</xsd:annotation>
<xsd:element name="user">
<xsd:annotation>
<xsd:documentation>
Container for emails, openids and photos elements.
This is the root element of the XML file.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="account" type="accountType"/>
<xsd:element name="emails" type="emailList"/>
<xsd:element name="openids" type="openidList"/>
<xsd:element name="photos" type="photoList"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="accountType">
<xsd:annotation>
<xsd:documentation>
Empty element holding user account-related information.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:restriction base="xsd:anyType">
<xsd:attribute name="username" type="xsd:string"/>
<xsd:attribute name="site" type="xsd:string"/>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="emailList">
<xsd:annotation>
<xsd:documentation>
Container for confirmed email addresses.
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="email" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="openidList">
<xsd:annotation>
<xsd:documentation>
Container for confirmed OpenID URLs.
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="openid" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="photoList">
<xsd:annotation>
<xsd:documentation>
Container for uploaded/imported photos.
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="photo" type="photoType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="photoType">
<xsd:annotation>
<xsd:documentation>
Container for a base64 encoded photo.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleContent>
<xsd:extension base="xsd:base64Binary">
<xsd:attribute name="encoding" type="xsd:string" fixed="base64"/>
<xsd:attribute name="format" type="photoFormat"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:simpleType name="photoFormat">
<xsd:annotation>
<xsd:documentation>
File type for the given photo.
</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="jpg"/>
<xsd:enumeration value="png"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="https://www.libravatar.org/schemas/export/0.2"
xmlns="https://www.libravatar.org/schemas/export/0.2"
elementFormDefault="qualified">
<xsd:annotation>
<xsd:documentation>
XML Schema for Libravatar user account exports.
Last Modifed 2013-01-12
</xsd:documentation>
</xsd:annotation>
<xsd:element name="user">
<xsd:annotation>
<xsd:documentation>
Container for emails, openids and photos elements.
This is the root element of the XML file.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="account" type="accountType"/>
<xsd:element name="emails" type="emailList"/>
<xsd:element name="openids" type="openidList"/>
<xsd:element name="photos" type="photoList"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="accountType">
<xsd:annotation>
<xsd:documentation>
Empty element holding user account-related information.
</xsd:documentation>
</xsd:annotation>
<xsd:complexContent>
<xsd:restriction base="xsd:anyType">
<xsd:attribute name="username" type="xsd:string"/>
<xsd:attribute name="site" type="xsd:string"/>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="emailList">
<xsd:annotation>
<xsd:documentation>
Container for confirmed email addresses.
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="email" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="openidList">
<xsd:annotation>
<xsd:documentation>
Container for confirmed OpenID URLs.
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="openid" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="photoList">
<xsd:annotation>
<xsd:documentation>
Container for uploaded/imported photos.
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="photo" type="photoType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>