Skip to content
Snippets Groups Projects
Commit 333b3b05 authored by kaiyou's avatar kaiyou
Browse files

Very first version of the captcha api

parent f4ebc51d
No related branches found
No related tags found
No related merge requests found
import flask
blueprint = flask.Blueprint("captcha", __name__, template_folder="templates")
from hiboo.captcha import views
from wtforms import widgets
from hiboo.captcha import fields
import random
class DummyCaptchaField(fields.CaptchaField):
""" Dummy captcha that displays a number and checks for it
This is meant as an example of a captcha implementation.
"""
# This is the rendered widget. If you wish something more specific, you
# may use custom widgets or implement __call__() for the field to render
# HTML directly
widget = widgets.TextInput()
def _value(self):
""" This is required by the TextInput widget (displayed value)
"""
return ""
def challenge(self):
""" Generate a challenge, here we generate a random integer and update
the label accordingly
"""
value = random.randint(100,1000)
self.label = "Please write down the number {}".format(value)
return value
def check(self, challenge, form, field):
""" Check that the field was properly filled with the challenge
"""
return (
field.data.isdigit() and
challenge == int(field.data)
)
from wtforms import validators, fields, widgets, utils
from jwcrypto import jwe, common
import flask
import random
class ContextTokenField(fields.Field):
""" Field that has a token hidden field that holds a stateless context.
"""
# TODO: implement context expiration
# TODO: implement context validity based on request attributes
@classmethod
def encode(cls, context):
""" JOSE authenticating encryption
"""
jose = jwe.JWE(
common.json_encode(context),
common.json_encode({"alg": "PBES2-HS512+A256KW", "enc": "A256GCM"})
)
jose.add_recipient(flask.current_app.config["SECRET_KEY"])
return jose.serialize(True)
@classmethod
def decode(cls, serialized):
""" JOSE decryption
"""
jose = jwe.JWE()
jose.deserialize(serialized)
jose.decrypt(flask.current_app.config["SECRET_KEY"])
return common.json_decode(jose.payload)
def __init__(self, *args, **kwargs):
self.received_context = {}
self.context = {}
# This is an internal field used to store stateless context between
# executions, context is protected using JOSE
self.token = fields.HiddenField("token")
super(ContextTokenField, self).__init__(*args, **kwargs)
def process(self, formdata=None, data=utils.unset_value):
""" Initialize the internal token field
"""
self.bound_token = self.token.bind(
form=None,
name="{}_token".format(self.name),
id="{}_token".format(self.id),
_meta=self.meta,
translations=self._translations
)
self.bound_token.process(formdata, data)
super(ContextTokenField, self).process(formdata, data)
def pre_validate(self, form):
""" Validate that the context properly decodes
"""
try:
decoded = ContextTokenField.decode(self.bound_token.data)
self.received_context = decoded
except:
raise validators.ValidationError("Could not decode the context")
def __call__(self, **kwargs):
""" Display the context token before the field itself
"""
self.bound_token.data = ContextTokenField.encode(self.context)
return widgets.HTMLString(
self.bound_token() +
super(ContextTokenField, self).__call__(**kwargs)
)
class CaptchaField(ContextTokenField):
""" Generic captcha field that requires the implementation of two
functions for simple captchas.
"""
def __init__(self, label=None, validators=None, *args, **kwargs):
validators = validators or [self.validate_captcha]
super(CaptchaField, self).__init__(label, validators, *args, **kwargs)
def process(self, formdata=None, data=utils.unset_value):
""" Initializes a challenge
"""
self.context["challenge"] = self.challenge()
super(CaptchaField, self).process(formdata, data)
def validate_captcha(self, form, field):
if "challenge" not in self.received_context:
raise validators.ValidationError("CAPTCHA session expired")
if not self.check(self.received_context["challenge"], form, field):
raise validators.ValidationError("Wrong CAPTCHA")
def challenge(self):
""" Implement this method to generate a challenge that will later be
rendered and validated.
"""
return None
def check(self, challenge, form, field):
""" Implement this method to check that the challenge has been resolved
properly.
"""
return True
from wtforms import fields
from hiboo.captcha import captcha
import flask_wtf
class DisplayForm(flask_wtf.FlaskForm):
test = fields.StringField()
captcha = captcha.DummyCaptchaField()
submit = fields.SubmitField()
{% extends "form.html" %}
from hiboo.captcha import blueprint, forms
import flask
@blueprint.route("/captcha", methods=["GET", "POST"])
def captcha():
form = forms.DisplayForm()
if form.validate_on_submit():
return "captcha is ok"
else:
return flask.render_template("captcha.html", form=form)
......@@ -19,3 +19,4 @@ cryptography
authlib
mysqlclient
psycopg2
jwcrypto
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment