This commit is contained in:
Oliver Falk
2018-05-23 10:39:18 +02:00
parent 6819aa7264
commit 6bd4080d58
3 changed files with 54 additions and 30 deletions

View File

@@ -17,6 +17,7 @@ from ipware import get_client_ip
MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5 MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5
class AddEmailForm(forms.Form): class AddEmailForm(forms.Form):
''' '''
Form to handle adding email addresses Form to handle adding email addresses
@@ -24,7 +25,7 @@ class AddEmailForm(forms.Form):
email = forms.EmailField( email = forms.EmailField(
label=_('Email'), label=_('Email'),
max_length=MAX_LENGTH_EMAIL, max_length=MAX_LENGTH_EMAIL,
min_length=6, # x@x.xx min_length=6, # x@x.xx
) )
def clean_email(self): def clean_email(self):
@@ -41,35 +42,44 @@ class AddEmailForm(forms.Form):
# Enforce the maximum number of unconfirmed emails a user can have # Enforce the maximum number of unconfirmed emails a user can have
num_unconfirmed = user.unconfirmedemail_set.count() num_unconfirmed = user.unconfirmedemail_set.count()
max_num_unconfirmed_emails = getattr(settings, 'MAX_NUM_UNCONFIRMED_EMAILS', MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT) max_num_unconfirmed_emails = getattr(
settings,
'MAX_NUM_UNCONFIRMED_EMAILS',
MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT)
if num_unconfirmed >= max_num_unconfirmed_emails: if num_unconfirmed >= max_num_unconfirmed_emails:
return False return False
# Check whether or not a confirmation email has been sent by this user already # Check whether or not a confirmation email has been
# sent by this user already
if UnconfirmedEmail.objects.filter( if UnconfirmedEmail.objects.filter(
user=user, email=self.cleaned_data['email']).exists(): user=user, email=self.cleaned_data['email']).exists():
return False return False
# Check whether or not the email is already confirmed by someone # Check whether or not the email is already confirmed by someone
if ConfirmedEmail.objects.filter( if ConfirmedEmail.objects.filter(
email=self.cleaned_data['email']).exists(): email=self.cleaned_data['email']).exists():
return False return False
unconfirmed = UnconfirmedEmail() unconfirmed = UnconfirmedEmail()
unconfirmed.email = self.cleaned_data['email'] unconfirmed.email = self.cleaned_data['email']
unconfirmed.user = user unconfirmed.user = user
unconfirmed.save() unconfirmed.save()
link = settings.SITE_URL + reverse('confirm_email', kwargs={'verification_key':unconfirmed.verification_key}) link = settings.SITE_URL + \
email_subject = _('Confirm your email address on %s') % settings.SITE_NAME reverse(
'confirm_email',
kwargs={'verification_key': unconfirmed.verification_key})
email_subject = _('Confirm your email address on %s') % \
settings.SITE_NAME
email_body = render_to_string('email_confirmation.txt', { email_body = render_to_string('email_confirmation.txt', {
'verification_link': link, 'verification_link': link,
'site_name': settings.SITE_NAME, 'site_name': settings.SITE_NAME,
}) })
#if settings.DEBUG: # if settings.DEBUG:
# print('DEBUG: %s' % link) # print('DEBUG: %s' % link)
send_mail(email_subject, email_body, settings.SERVER_EMAIL, send_mail(
email_subject, email_body, settings.SERVER_EMAIL,
[unconfirmed.email]) [unconfirmed.email])
return True return True
@@ -86,14 +96,16 @@ class UploadPhotoForm(forms.Form):
required=True, required=True,
error_messages={ error_messages={
'required': '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( can_distribute = forms.BooleanField(
label=_('can be freely copied'), label=_('can be freely copied'),
required=True, required=True,
error_messages={ error_messages={
'required': '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.')
}) })
def save(self, request, data): def save(self, request, data):
@@ -110,6 +122,7 @@ class UploadPhotoForm(forms.Form):
return None return None
return photo return photo
class AddOpenIDForm(forms.Form): class AddOpenIDForm(forms.Form):
''' '''
Form to handle adding OpenID Form to handle adding OpenID
@@ -119,7 +132,7 @@ class AddOpenIDForm(forms.Form):
max_length=MAX_LENGTH_URL, max_length=MAX_LENGTH_URL,
# However, not 100% sure if single character domains are possible # However, not 100% sure if single character domains are possible
# under any tld... # under any tld...
min_length=11, # eg. http://a.io min_length=11, # eg. http://a.io
initial='http://' initial='http://'
) )
@@ -129,8 +142,9 @@ class AddOpenIDForm(forms.Form):
''' '''
# Lowercase hostname port of the URL # Lowercase hostname port of the URL
url = urlsplit(self.cleaned_data['openid']) url = urlsplit(self.cleaned_data['openid'])
data = urlunsplit((url.scheme.lower(), url.netloc.lower(), url.path, data = urlunsplit(
url.query, url.fragment)) (url.scheme.lower(), url.netloc.lower(), url.path,
url.query, url.fragment))
# TODO: Domain restriction as in libravatar? # TODO: Domain restriction as in libravatar?
@@ -141,11 +155,11 @@ class AddOpenIDForm(forms.Form):
Save the model, ensuring some safety Save the model, ensuring some safety
''' '''
if ConfirmedOpenId.objects.filter( if ConfirmedOpenId.objects.filter(
openid=self.cleaned_data['openid']).exists(): openid=self.cleaned_data['openid']).exists():
return False return False
if UnconfirmedOpenId.objects.filter( if UnconfirmedOpenId.objects.filter(
openid=self.cleaned_data['openid']).exists(): openid=self.cleaned_data['openid']).exists():
return False return False
unconfirmed = UnconfirmedOpenId() unconfirmed = UnconfirmedOpenId()

View File

@@ -4,14 +4,15 @@ import hashlib
URL_TIMEOUT = 5 # in seconds URL_TIMEOUT = 5 # in seconds
def get_photo(email): def get_photo(email):
''' '''
Fetch photo from Gravatar, given an email address Fetch photo from Gravatar, given an email address
''' '''
hash_object = hashlib.new('md5') hash_object = hashlib.new('md5')
hash_object.update(email.lower().encode('utf-8')) hash_object.update(email.lower().encode('utf-8'))
thumbnail_url = 'https://secure.gravatar.com/avatar/' + hash_object.hexdigest( thumbnail_url = 'https://secure.gravatar.com/avatar/' + \
) + '?s=80&d=404' hash_object.hexdigest() + '?s=80&d=404'
image_url = 'https://secure.gravatar.com/avatar/' + hash_object.hexdigest( image_url = 'https://secure.gravatar.com/avatar/' + hash_object.hexdigest(
) + '?s=512&d=404' ) + '?s=512&d=404'
@@ -22,14 +23,19 @@ def get_photo(email):
urlopen(image_url, timeout=URL_TIMEOUT) urlopen(image_url, timeout=URL_TIMEOUT)
except HTTPError as e: except HTTPError as e:
if e.code != 404 and e.code != 503: if e.code != 404 and e.code != 503:
print('Gravatar fetch failed with an unexpected %s HTTP error' % # pragma: no cover print( # pragma: no cover
e.code) 'Gravatar fetch failed with an unexpected %s HTTP error' %
e.code)
return False return False
except URLError as e: # pragma: no cover except URLError as e: # pragma: no cover
print('Gravatar fetch failed with URL error: %s' % e.reason) # pragma: no cover print(
'Gravatar fetch failed with URL error: %s' %
e.reason) # pragma: no cover
return False # pragma: no cover return False # pragma: no cover
except SSLError as e: # pragma: no cover except SSLError as e: # pragma: no cover
print('Gravatar fetch failed with SSL error: %s' % e.reason) # pragma: no cover print(
'Gravatar fetch failed with SSL error: %s' %
e.reason) # pragma: no cover
return False # pragma: no cover return False # pragma: no cover
return { return {

View File

@@ -20,7 +20,7 @@ from openid.association import Association as OIDAssociation
from openid.store import nonce as oidnonce from openid.store import nonce as oidnonce
from openid.store.interface import OpenIDStore from openid.store.interface import OpenIDStore
from ivatar.settings import MAX_LENGTH_EMAIL from ivatar.settings import MAX_LENGTH_EMAIL, logger
from .gravatar import get_photo as get_gravatar_photo from .gravatar import get_photo as get_gravatar_photo
@@ -38,7 +38,7 @@ def file_format(image_type):
elif image_type == 'GIF': elif image_type == 'GIF':
return 'gif' return 'gif'
print('Unsupported file format: %s' % image_type) logger.info('Unsupported file format: %s' % image_type)
return None return None
@@ -188,7 +188,9 @@ class ConfirmedEmail(BaseAccountModel):
''' '''
Override save from parent, add digest Override save from parent, add digest
''' '''
self.digest = hashlib.md5(self.email.strip().lower().encode('utf-8')).hexdigest() self.digest = hashlib.md5(
self.email.strip().lower().encode('utf-8')
).hexdigest()
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
@@ -254,7 +256,9 @@ class ConfirmedOpenId(BaseAccountModel):
netloc = url.username + ':' + password + '@' + url.hostname netloc = url.username + ':' + password + '@' + url.hostname
else: else:
netloc = url.hostname netloc = url.hostname
lowercase_url = urlunsplit((url.scheme.lower(), netloc, url.path, url.query, url.fragment)) lowercase_url = urlunsplit(
(url.scheme.lower(), netloc, url.path, url.query, url.fragment)
)
self.digest = hashlib.sha256(lowercase_url.encode('utf-8')).hexdigest() self.digest = hashlib.sha256(lowercase_url.encode('utf-8')).hexdigest()
return super().save(*args, **kwargs) return super().save(*args, **kwargs)