Merge branch 'master' of git.linux-kernel.at:oliver/ivatar into trust

This commit is contained in:
Oliver Falk
2019-05-08 10:33:44 +02:00
18 changed files with 121 additions and 34 deletions

View File

@@ -1,7 +1,7 @@
image: ofalk/centos7-python36
image: docker.io/ofalk/fedora28-python3
before_script:
- virtualenv -p python3.6 /tmp/.virtualenv
- virtualenv-3 -p python3 /tmp/.virtualenv
- source /tmp/.virtualenv/bin/activate
- pip install Pillow
- pip install -r requirements.txt

View File

@@ -1,5 +1,25 @@
ivatar / libravatar
===================
Pipeline and coverage status
============================
[![pipeline status](https://git.linux-kernel.at/oliver/ivatar/badges/master/pipeline.svg)](https://git.linux-kernel.at/oliver/ivatar/commits/master)
[![coverage report](https://git.linux-kernel.at/oliver/ivatar/badges/master/coverage.svg)](http://git.linux-kernel.at/oliver/ivatar/commits/master)
Reports / code documentation
============================
- [Coverage HTML report](http://oliver.git.linux-kernel.at/ivatar)
- [Code documentation (autogenerated, pycco)](http://oliver.git.linux-kernel.at/ivatar/pycco/)
Authors and contributors
========================
Lead developer/Owner: Oliver Falk (aka ofalk or falko) - https://git.linux-kernel.at/oliver
Operations: Michal Novotny (aka clime)
QA: Tristan Le Guern (aka tleguern)
Frontend developer: Niklas Poslovski (aka nipos)
Organisation/Meeting moderation: Lars Kruse (aka sumpfralle)
Initial developer: François Marier - https://fmarier.org/

View File

@@ -187,3 +187,7 @@ CACHES = {
],
}
}
# This is 5 minutes caching for generated/resized images,
# so the sites don't hit ivatar so much
CACHE_IMAGES_MAX_AGE = 5 * 60

View File

@@ -28,13 +28,13 @@
<div class="form-group">
<div class="checkbox">
<input type="checkbox" name="not_porn" required id="id_not_porn">
<label for="id_not_porn">{% trans 'suitable for all ages (i.e. no offensive content)' %}</label>
<label for="id_not_porn"><b>{% trans 'required' %}</b>; {% trans 'suitable for all ages (i.e. no offensive content)' %}</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" name="can_distribute" required id="id_can_distribute">
<label for="id_can_distribute">{% trans 'can be freely copied' %}</label>
<label for="id_can_distribute"><b>{% trans 'required' %}</b>; {% trans 'can be freely copied' %}</label>
</div>
</div>
<button type="submit" class="btn btn-default">{% trans 'Upload' %}</button>

View File

@@ -816,12 +816,16 @@ class UploadLibravatarExportView(SuccessMessageMixin, FormView):
def form_valid(self, form):
data = self.request.FILES['export_file']
try:
items = libravatar_read_gzdata(data.read())
# DEBUG print(items)
return render(self.request, 'choose_libravatar_export.html', {
'emails': items['emails'],
'photos': items['photos'],
})
except Exception as e:
messages.error(self.request, _('Unable to parse file: %s' % e))
return HttpResponseRedirect(reverse_lazy('upload_export'))
@method_decorator(login_required, name='dispatch')
@@ -914,7 +918,7 @@ 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:
if not confirmed_email.user.password or confirmed_email.user.password == '!':
random_pass = User.objects.make_random_password()
confirmed_email.user.set_pasword(random_pass)
confirmed_email.user.save()

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
@import 'tortin.less';
@bg-hero:#ff4400;
.btn {
border-radius: 0px !important;
}
section.content h1, section.content h2, section.content h3, section.content h4, section.content h5, section.content h6 {
color: #ff4400;
}

View File

@@ -61,5 +61,8 @@ class Tester(TestCase): # pylint: disable=too-many-public-methods
"""
Test incorrect digest
"""
response = self.client.get('/avatar/%s' % 'x'*65)
self.assertEqual(response.status_code, 200, 'no 200 ok?')
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?')

View File

@@ -21,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>
@@ -44,7 +44,7 @@
{% 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>

View File

@@ -34,7 +34,6 @@
</form>
</div>
<!-- TODO TODO TODO: I need better styling -->
{% if result %}
<hr/>
<h2>The following servers will be used for your domain</h2>
@@ -44,8 +43,8 @@
</div>
<div class="panel-body">
{% if result.avatar_server_http %}
<a href="{{result.avatar_server_http}}">
<h4>{{result.avatar_server_http}}</h4>
<a href="http://{{result.avatar_server_http}}">
<h4>http://{{result.avatar_server_http}}</h4>
</a>
{% if result.avatar_server_http_ipv4 %}
<br><center>{{ result.avatar_server_http_ipv4 }}</center>
@@ -66,8 +65,8 @@
</div>
<div class="panel-body">
{% if result.avatar_server_https %}
<a href="{{result.avatar_server_https}}">
<h4>{{result.avatar_server_https}}</h4>
<a href="https://{{result.avatar_server_https}}">
<h4>https://{{result.avatar_server_https}}</h4>
</a>
{% if result.avatar_server_https_ipv4 %}
<br><center>{{ result.avatar_server_https_ipv4 }}</center>

View File

@@ -8,4 +8,5 @@ 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'),
]

View File

@@ -21,13 +21,10 @@ urlpatterns = [ # pylint: disable=invalid-name
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*)',
TemplateView.as_view(
template_name='error.html',
extra_context={
'errormessage': 'Incorrect digest length',
})),
RedirectView.as_view(url='/static/img/deadbeef.png'), name='invalid_hash'),
url(
r'gravatarproxy/(?P<digest>\w*)',
GravatarProxyView.as_view(), name='gravatarproxy'),

View File

@@ -22,6 +22,7 @@ import pagan
from robohash import Robohash
from ivatar.settings import AVATAR_MAX_SIZE, JPEG_QUALITY, DEFAULT_AVATAR_SIZE
from ivatar.settings import CACHE_IMAGES_MAX_AGE
from . ivataraccount.models import ConfirmedEmail, ConfirmedOpenId
from . ivataraccount.models import pil_format, file_format
@@ -58,6 +59,11 @@ 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
response = HttpResponse("", content_type='text/plain')
response['Allow'] = "404 mm mp retro pagan wavatar monsterid robohash identicon"
return response
def get(self, request, *args, **kwargs): # pylint: disable=too-many-branches,too-many-statements,too-many-locals,too-many-return-statements
'''
Override get from parent class
@@ -142,9 +148,11 @@ class AvatarImageView(TemplateView):
data = BytesIO()
monsterdata.save(data, 'PNG', quality=JPEG_QUALITY)
data.seek(0)
return HttpResponse(
response = HttpResponse(
data,
content_type='image/png')
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
if str(default) == 'robohash':
roboset = 'any'
@@ -155,9 +163,11 @@ class AvatarImageView(TemplateView):
data = BytesIO()
robohash.img.save(data, format='png')
data.seek(0)
return HttpResponse(
response = HttpResponse(
data,
content_type='image/png')
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
if str(default) == 'retro':
identicon = Identicon.render(kwargs['digest'])
@@ -166,9 +176,11 @@ class AvatarImageView(TemplateView):
img = img.resize((size, size), Image.ANTIALIAS)
img.save(data, 'PNG', quality=JPEG_QUALITY)
data.seek(0)
return HttpResponse(
response = HttpResponse(
data,
content_type='image/png')
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
if str(default) == 'pagan':
paganobj = pagan.Avatar(kwargs['digest'])
@@ -176,9 +188,11 @@ class AvatarImageView(TemplateView):
img = paganobj.img.resize((size, size), Image.ANTIALIAS)
img.save(data, 'PNG', quality=JPEG_QUALITY)
data.seek(0)
return HttpResponse(
response = HttpResponse(
data,
content_type='image/png')
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
if str(default) == 'identicon':
p = Pydenticon5()
@@ -188,9 +202,11 @@ class AvatarImageView(TemplateView):
data = BytesIO()
img.save(data, 'PNG', quality=JPEG_QUALITY)
data.seek(0)
return HttpResponse(
response = HttpResponse(
data,
content_type='image/png')
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
if str(default) == 'mm' or str(default) == 'mp':
# If mm is explicitly given, we need to catch that
@@ -226,9 +242,11 @@ class AvatarImageView(TemplateView):
obj.save()
if imgformat == 'jpg':
imgformat = 'jpeg'
return HttpResponse(
response = HttpResponse(
data,
content_type='image/%s' % imgformat)
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
class GravatarProxyView(View):
'''
@@ -297,9 +315,11 @@ class GravatarProxyView(View):
data = BytesIO(gravatarimagedata.read())
img = Image.open(data)
data.seek(0)
return HttpResponse(
response = HttpResponse(
data.read(),
content_type='image/%s' % file_format(img.format))
response['Cache-Control'] = 'max-age=%i' % CACHE_IMAGES_MAX_AGE
return response
except ValueError as exc:
print('Value error: %s' % exc)

22
libravatarproxy.py Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python3
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'))
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')))
sys.stdout.flush()
break
sys.stdout.buffer.write(data)

View File

@@ -30,7 +30,7 @@ wheel
yapf
django-anymail[mailgun]
mysqlclient
psycopg2
psycopg2-binary
notsetuptools
git+https://github.com/ofalk/monsterid.git
git+https://github.com/ofalk/Robohash.git@devel

View File

@@ -30,6 +30,10 @@ If you've got a proposal to discuss or prefer to write to us, you can join our <
You can also put short notices to our attention on <a href="http://identi.ca/libravatar" title="http://identi.ca/libravatar">Identica<a/> or <a href="http://twitter.com/libravatar" title="http://twitter.com/libravatar">Twitter</a>.
<h4>Mastodon</h4>
Our Mastodon profile is available on <a href="https://photog.social/@libravatar">https://photog.social/@libravatar</a>.
<h4>Email</h4>
Finally, if you need to email us: <a href="mailto:dev@libravatar.org" title="mailto:dev@libravatar.org">dev@libravatar.org</a>

View File

@@ -31,6 +31,7 @@
{% endif %}
{% endif %}
<link rel="manifest" href="/manifest.json">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="msapplication-TileImage" content="{% static '/img/nobody/144.png' %}">
<meta name="msapplication-TileColor" content="#36b7d7">
<meta name="apple-mobile-web-app-title" content="ivatar">

View File

@@ -37,7 +37,7 @@
</ul>
</div>
{% endif %}
<a href="/tools/check/" class="btn btn-lg btn-primary">{% trans 'Check email' %}</a>&nbsp;
&nbsp;<a href="/tools/check/" class="btn btn-lg btn-primary">{% trans 'Check' %}</a>&nbsp;
</header>
</div>
</div>
@@ -71,6 +71,7 @@
<a class="btn btn-default" href="https://wiki.libravatar.org/">{% trans 'Wiki' %}</a><br/>
<a class="btn btn-default" href="http://blog.libravatar.org/">{% trans 'Blog' %}</a><br/>
<h3>{% trans 'Social media' %}</h3>
<a class="btn btn-default" rel="me" href="https://photog.social/@libravatar">Mastodon</a><br/>
<a class="btn btn-default" href="https://identi.ca/libravatar">Identica</a><br/>
<a class="btn btn-default" href="https://twitter.com/libravatar">Twitter</a><br/>
</section>