All responses are now json. Added a honeypot field

This commit is contained in:
Adrian Amaglio 2020-09-16 14:49:15 +02:00
parent 3e4e3854d4
commit 4fe3ce5652
3 changed files with 54 additions and 55 deletions

View File

@ -1,6 +0,0 @@
<h2>Liste</h2>
<ul>
% for item in data:
<li>{{item}}</li>
% end
</ul>

97
main.py
View File

@ -11,7 +11,6 @@ import pymongo # database
from dotenv import load_dotenv from dotenv import load_dotenv
import random, string # for tokens import random, string # for tokens
import html # for sanitization import html # for sanitization
import datetime # to name unsent mails
from bson.json_util import dumps from bson.json_util import dumps
@ -114,7 +113,7 @@ def submission ():
token = request.forms.getunicode('token') token = request.forms.getunicode('token')
else: else:
response.status = 400 response.status = 400
return 'Le jeton dautentification est requis' return resp('error', 'Le jeton dautentification est requis')
# Getting mail address # Getting mail address
if 'mail' in request.forms: if 'mail' in request.forms:
@ -128,19 +127,22 @@ def submission ():
form = mongodb_database['forms'].find({'token': token})[0] form = mongodb_database['forms'].find({'token': token})[0]
except IndexError as e: except IndexError as e:
response.status = 400 response.status = 400
return 'Le formulaire demandé est introuvable, merci de vérifier que le token utilisé est le bon' return resp('error', 'Le formulaire demandé est introuvable, merci de vérifier que le token utilisé est le bon')
except pymongo.errors.ServerSelectionTimeoutError as e: except pymongo.errors.ServerSelectionTimeoutError as e:
response.status = 500 response.status = 500
return 'La base de donnée nest pas accessible.' return resp('error', 'La base de donnée nest pas accessible.')
try: try:
subject_fields = fill_fields(request, get_fields(form['subject'])) subject_fields = fill_fields(request, get_fields(form['subject']))
content_fields = fill_fields(request, get_fields(form['content'])) content_fields = fill_fields(request, get_fields(form['content']))
print(subject_fields) # Did the bot filled the honeypot field?
print(content_fields) if 'honeypotfield' in form and form['honeypotfield'] in request.forms and request.forms.get(form['honeypotfield']) != '':
response.status = 400
return resp('error', 'We identified you as a bot. If this is an error, try to contact us via another way.')
except MissingParameterException as e: except MissingParameterException as e:
response.status = 404 response.status = 404
return str(e) return resp('error', str(e))
subject = re.sub(form_regex, r'{\1}', form['subject']).format(**subject_fields) subject = re.sub(form_regex, r'{\1}', form['subject']).format(**subject_fields)
content = re.sub(form_regex, r'{\1}', form['content']).format(**content_fields) content = re.sub(form_regex, r'{\1}', form['content']).format(**content_fields)
@ -148,33 +150,26 @@ def submission ():
try: try:
if not send_mail(from_address, form['mail'], subject, content): if not send_mail(from_address, form['mail'], subject, content):
response.status = 500 response.status = 500
return 'Le mail na pas pu être envoyé.' return resp('error', 'Le mail na pas pu être envoyé.')
except smtplib.SMTPDataError as e: except smtplib.SMTPDataError as e:
save_mail (token, form['mail'], from_address, subject, content)
response.status = 500 response.status = 500
error = 'Le mail a été refusé. Votre message a été enregistré, il sera remis manuellement à son destinataire.' error = 'Le mail a été refusé. Merci de réessayer plus tard.'
except smtplib.SMTPRecipientsRefused as e: except smtplib.SMTPRecipientsRefused as e:
save_mail (token, form['mail'], from_address, subject, content)
response.status = 500 response.status = 500
error = 'Impossible de trouver le destinataire du mail. Votre message a été enregistré, il sera remis manuellement à son destinataire.' error = 'Impossible de trouver le destinataire du mail. Merci de réessayer plus tard'
except Exception as e: except Exception as e:
save_mail (token, form['mail'], from_address, subject, content)
raise raise
# Redirection # Redirection
#bottle.redirect(success_redirect_default) #bottle.redirect(success_redirect_default)
origin = request.headers.get('origin') origin = request.headers.get('origin')
return '<p>Mail envoyé !</p>' + ('<p>Retour au <a href="{}">formulaire de contact</a></p>'.format(origin) if origin else '') return resp('success', 'Mail envoyé !')
##################################################### Helpers ############################################ ##################################################### Helpers ############################################
def save_mail (token, to, from_address, subject, content):
with open('unsent/unsent_{}_{}_{}.txt'.format(str(datetime.datetime.now()), token, to), 'w') as f: def resp (status, msg, data='{}'):
f.write("Unsent mail\nSubject: {}\nFrom: {}Content:\n{}".format( return '{{"status": "{}", "msg": "{}", "data": {}}}'.format(status, msg, data)
subject,
from_address,
content
))
def get_fields (string): def get_fields (string):
""" Parse the string looking for template elements and create an array with template to fill and their default values. None if mandatory. """ """ Parse the string looking for template elements and create an array with template to fill and their default values. None if mandatory. """
@ -255,26 +250,31 @@ def create_form ():
subject = mail_default_subject subject = mail_default_subject
else: else:
response.status = 400 response.status = 400
return 'Le champs « sujet » est requis' return resp('error', 'Le champs « sujet » est requis')
# Getting mail content # Getting mail content
if 'content' in request.forms: if 'content' in request.forms:
content = request.forms.getunicode('content') content = request.forms.getunicode('content')
else: else:
response.status = 400 response.status = 400
return 'Le champs « contenu » est requis' return resp('error', 'Le champs « contenu » est requis')
if 'honeypotfield' in request.forms:
honeypotfield = request.forms.getunicode('honeypotfield')
else:
honeypotfield = None
# Getting from address # Getting from address
if 'mail' in request.forms: if 'mail' in request.forms:
mail = request.forms.getunicode('mail') mail = request.forms.getunicode('mail')
else: else:
response.status = 400 response.status = 400
return 'Le champs « adresse » est requis' return resp('error', 'Le champs « adresse » est requis')
user = login(request) user = login(request)
if user['_privilege'] > 1: if user['_privilege'] > 1:
response.status = 400 response.status = 400
return 'Privilèges insufisants' return resp('error', 'Privilèges insufisants')
# TODO limit the insertion rate # TODO limit the insertion rate
token = ''.join(random.sample(token_chars, token_len)) token = ''.join(random.sample(token_chars, token_len))
@ -285,12 +285,13 @@ def create_form ():
'subject': subject, 'subject': subject,
'user_id': user['_id'], 'user_id': user['_id'],
'token': token, 'token': token,
'honeypotfield': honeypotfield,
}) })
except pymongo.errors.ServerSelectionTimeoutError as e: except pymongo.errors.ServerSelectionTimeoutError as e:
response.status = 500 response.status = 500
return 'La base de donnée nest pas accessible' return resp('error', 'La base de donnée nest pas accessible')
return 'Créé : ' + token return resp('success', 'Créé : ' + token)
@app.post('/form/list') @app.post('/form/list')
def list_forms (): def list_forms ():
@ -302,12 +303,12 @@ def list_forms ():
filt = {'user_id': user['_id']} filt = {'user_id': user['_id']}
else: else:
response.status = 400 response.status = 400
return 'Privilèges insufisants' return resp('error', 'Privilèges insufisants')
data = mongodb_database['forms'].find(filt) data = mongodb_database['forms'].find(filt)
return dumps(list(data)) return resp('success','', dumps(list(data)))
except pymongo.errors.ServerSelectionTimeoutError as e: except pymongo.errors.ServerSelectionTimeoutError as e:
response.status = 500 response.status = 500
return 'La base de donnée nest pas accessible' return resp('error','La base de donnée nest pas accessible')
@ -317,17 +318,17 @@ def delete_form(token):
user = login(request) user = login(request)
if user['_privilege'] > 1: if user['_privilege'] > 1:
response.status = 400 response.status = 400
return 'Privilèges insufisants' return resp('error', 'Privilèges insufisants')
# Actually delete # Actually delete
try: try:
form = mongodb_database['forms'].find({'token':token })[0] form = mongodb_database['forms'].find({'token':token })[0]
except IndexError as e: except IndexError as e:
response.status = 400 response.status = 400
return 'Le token nest pas valide' return resp('error', 'Le token nest pas valide')
except pymongo.errors.ServerSelectionTimeoutError as e: except pymongo.errors.ServerSelectionTimeoutError as e:
response.status = 500 response.status = 500
return 'La base de donnée nest pas accessible' return resp('error', 'La base de donnée nest pas accessible')
if user['_privilege'] == 0 or (form['user_id'] == user['_id']): if user['_privilege'] == 0 or (form['user_id'] == user['_id']):
try: try:
@ -336,10 +337,10 @@ def delete_form(token):
}) })
except pymongo.errors.ServerSelectionTimeoutError as e: except pymongo.errors.ServerSelectionTimeoutError as e:
response.status = 500 response.status = 500
return 'La base de donnée nest pas accessible' return resp('error', 'La base de donnée nest pas accessible')
return 'Supprimé ' + token return resp('success', 'Supprimé ' + token)
response.status = 400 response.status = 400
return 'Privilèges insufisants' return resp('error', 'Privilèges insufisants')
##################################################### Users ############################################ ##################################################### Users ############################################
@ -349,13 +350,13 @@ def list_users ():
user = login(request) user = login(request)
if user['_privilege'] > 0: if user['_privilege'] > 0:
response.status = 400 response.status = 400
return 'Privilèges insufisants' return resp('error', 'Privilèges insufisants')
try: try:
data = mongodb_database['users'].find() data = mongodb_database['users'].find()
return dumps(list(data)) return resp('success', '', dumps(list(data)))
except pymongo.errors.ServerSelectionTimeoutError as e: except pymongo.errors.ServerSelectionTimeoutError as e:
response.status = 500 response.status = 500
return 'La base de donnée nest pas accessible' return resp('error', 'La base de donnée nest pas accessible')
@app.route('/user/<username>', method=['OPTIONS', 'PUT']) @app.route('/user/<username>', method=['OPTIONS', 'PUT'])
@ -363,23 +364,23 @@ def create_user (username):
user = login(request) user = login(request)
if user['_privilege'] > 0: if user['_privilege'] > 0:
response.status = 400 response.status = 400
return 'Privilèges insufisants' return resp('error', 'Privilèges insufisants')
try: try:
mongodb_database['users'].find({'username': username})[0] mongodb_database['users'].find({'username': username})[0]
return 'Lutilisateur existe déjà' return resp('error', 'Lutilisateur existe déjà')
except IndexError as e: except IndexError as e:
try: try:
inserted = mongodb_database['users'].insert_one({ inserted = mongodb_database['users'].insert_one({
'username': username, 'username': username,
'token': ''.join(random.sample(token_chars, token_len)) 'token': ''.join(random.sample(token_chars, token_len))
}) })
return 'Créé : ' + username return resp('success', 'Créé : ' + username)
except pymongo.errors.ServerSelectionTimeoutError as e: except pymongo.errors.ServerSelectionTimeoutError as e:
response.status = 500 response.status = 500
return 'La base de donnée nest pas accessible' return resp('error', 'La base de donnée nest pas accessible')
except pymongo.errors.ServerSelectionTimeoutError as e: except pymongo.errors.ServerSelectionTimeoutError as e:
response.status = 500 response.status = 500
return 'La base de donnée nest pas accessible' return resp('error','La base de donnée nest pas accessible')
@app.delete('/user/<username>') @app.delete('/user/<username>')
@ -387,19 +388,19 @@ def delete_user (username):
user = login(request) user = login(request)
if user['_privilege'] > 0: if user['_privilege'] > 0:
response.status = 400 response.status = 400
return 'Privilèges insufisants' return resp('error', 'Privilèges insufisants')
try: try:
mongodb_database['users'].find({'username': username})[0] mongodb_database['users'].find({'username': username})[0]
mongodb_database['users'].delete_one({ mongodb_database['users'].delete_one({
'username': username, 'username': username,
}) })
return 'Supprimé ' + username return resp('success', 'Supprimé ' + username)
except IndexError as e: except IndexError as e:
response.status = 400 response.status = 400
return 'Lutilisateur nexiste pas' return resp('error', 'Lutilisateur nexiste pas')
except pymongo.errors.ServerSelectionTimeoutError as e: except pymongo.errors.ServerSelectionTimeoutError as e:
response.status = 500 response.status = 500
return 'La base de donnée nest pas accessible' return resp('error', 'La base de donnée nest pas accessible')

View File

@ -7,11 +7,15 @@
<body> <body>
<div id="contact-mailer-message"></div> <div id="contact-mailer-message"></div>
<form action="http://localhost:8080/submit" method="POST" id="contact-mailer-form"> <form action="http://localhost:8080/submit" method="POST" id="contact-mailer-form">
<input type="hidden" name="token" value="M9zyPf4sm6opGe58vbNql0S2UktQIdBOYAaL1VJhKXFwxnRZug" /> <input type="hidden" name="token" value="sYMXDz5UKuRF38LbQl20ikrmp7nhHcxTCgGZodqAaBtSvPOV4f" />
<div> <div>
<label for="nom">Votre nom&nbsp;:</label> <label for="nom">Votre nom&nbsp;:</label>
<input type="text" name="nom" required="required"/> <input type="text" name="nom" required="required"/>
</div> </div>
<div>
<label for="nom">Votre prénom&nbsp;:</label>
<input type="text" name="prenom"/>
</div>
<div> <div>
<label for="mail">Adresse mail&nbsp;:</label> <label for="mail">Adresse mail&nbsp;:</label>
<input type="email" name="mail" required="required"/> <input type="email" name="mail" required="required"/>