mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-18 22:18:02 +00:00
Merge branch 'master' of git.linux-kernel.at:oliver/ivatar into trust
This commit is contained in:
@@ -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
|
||||
|
||||
20
README.md
20
README.md
@@ -1,5 +1,25 @@
|
||||
ivatar / libravatar
|
||||
===================
|
||||
|
||||
Pipeline and coverage status
|
||||
============================
|
||||
|
||||
[](https://git.linux-kernel.at/oliver/ivatar/commits/master)
|
||||
[](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/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
1
ivatar/static/css/clime.css
Normal file
1
ivatar/static/css/clime.css
Normal file
File diff suppressed because one or more lines are too long
10
ivatar/static/css/clime.less
Normal file
10
ivatar/static/css/clime.less
Normal 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;
|
||||
}
|
||||
@@ -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?')
|
||||
|
||||
@@ -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> <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> <i class="fa fa-openid" title="openid: {{ form.openid.value }}"></i></h3>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
22
libravatarproxy.py
Executable 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<a href="/tools/check/" class="btn btn-lg btn-primary">{% trans 'Check email' %}</a>
|
||||
<a href="/tools/check/" class="btn btn-lg btn-primary">{% trans 'Check' %}</a>
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user