diff --git a/etc/nextcloudregister/config.ini b/etc/nextcloudregister/config.ini index fe4891a..23332dc 100644 --- a/etc/nextcloudregister/config.ini +++ b/etc/nextcloudregister/config.ini @@ -46,4 +46,7 @@ max_accounts = 50 mandatory_email = yes # whether or not the user must give an password to subscribe # at least one of email and password is mandatory -mandatory_password = yes \ No newline at end of file +mandatory_password = yes +# path to db managing the captcha +db_path=nextcloudregister.sqli +captcha_timout=3600 \ No newline at end of file diff --git a/templates/form.html b/templates/form.html index 1f6f804..da0da44 100644 --- a/templates/form.html +++ b/templates/form.html @@ -42,6 +42,8 @@ {% endif %} + + {% if eula %}
En m'inscrivant à ce service, j'accepte ses Conditions Générales d'utilisation diff --git a/webapp.py b/webapp.py index d08a78d..fe5d434 100644 --- a/webapp.py +++ b/webapp.py @@ -1,4 +1,9 @@ from configparser import ConfigParser +import operator +from os.path import exists +import random +from secrets import token_urlsafe +from time import time from flask import ( Flask, @@ -6,6 +11,8 @@ from flask import ( render_template, Response, ) +import sqlalchemy +import sqlalchemy.orm from nextcloudregister.lib import ( CONFIG_PATH, @@ -24,16 +31,73 @@ config.read(CONFIG_PATH) app = Flask(__name__) base_uri = config.get("web", "base_uri", fallback="") base_uri = base_uri + ("" if base_uri.endswith("/") else "/") +db_path = config.get("rules", "db_path", fallback="db.sqlite") +Base = sqlalchemy.orm.declarative_base() + +OPERATORS = ( + ('+', operator.add), + ('×', operator.mul), + ('-', operator.sub), +) -@app.route(base_uri, methods=["GET", "POST"]) -def form_manager(): - if request.method == "POST": - return form_post() - else: - return form_get() +class Captcha(Base): + __tablename__ = "captcha" + + token = sqlalchemy.Column(sqlalchemy.String(90), primary_key=True) + answer = sqlalchemy.Column(sqlalchemy.String(3)) + expiration = sqlalchemy.Column(sqlalchemy.Integer()) +def get_session(): + engine = sqlalchemy.create_engine(f"sqlite:///{db_path}") + if not exists(db_path): + Base.metadata.create_all(engine) + session_factory = sqlalchemy.orm.sessionmaker(engine) + return session_factory() + + +def clean_db(session): + session.query(Captcha).filter(Captcha.expiration < int(time())).delete() + + +def generate_captacha(): + session = get_session() + clean_db(session) + first_number = random.randrange(10) + second_number = random.randrange(10) + op_text, op_func = random.choice(OPERATORS) + if op_text == "-" and first_number < second_number: + first_number, second_number = second_number, first_number + result = op_func(first_number, second_number) + captcha = Captcha( + token=token_urlsafe(67), + answer=str(result), + expiration=int(time()) + config.getint("rules", "captcha_timout", fallback=3600), + ) + session.add(captcha) + session.commit() + return { + "first_number": first_number, + "second_number": second_number, + "op_text": op_text, + "token": captcha.token, + } + + +def validate_captcha(token, value): + result = False + session = get_session() + clean_db(session) + captcha = session.query(Captcha).filter(Captcha.token == token).one_or_none() + if captcha: + result = value == captcha.answer + session.query(Captcha).filter(Captcha.token == token).delete() + session.commit() + return result + + +@app.route(base_uri, methods=["GET"]) def form_get(data=None, error=None, info=None, success=False): context = { "base_uri": base_uri, @@ -63,18 +127,21 @@ def form_get(data=None, error=None, info=None, success=False): try: count_accounts = api.count_accounts() except NextcloudApiException: - context["max_accounts"] = 0 + context["count_accounts"] = context["max_accounts"] if count_accounts >= context["max_accounts"] and not success: context["disable"] = True context["error"] = ( "Tous les comptes disponibles sur cette instance ont deja été " "distribués." ) + context["count_accounts"] = 0 else: context["count_accounts"] = context["max_accounts"] - count_accounts + context |= generate_captacha() return render_template("form.html", **context) +@app.route(base_uri, methods=["POST"]) def form_post(): mandatory_email = config.getboolean( "rules", @@ -86,6 +153,13 @@ def form_post(): "mandatory_password", fallback=False, ) + # validate captcha + if not validate_captcha(token=request.form.get("token"), value=request.form.get("answer")): + return form_get( + data=request.form, + error="Le captcha est invalide." + ) + # validate mandatory fields if not request.form.get("username"): return form_get( @@ -115,6 +189,19 @@ def form_post(): ) api = NextcloudApi() + try: + if api.count_accounts() >= config.getint("rules", "max_accounts", fallback=0): + return form_get( + data=request.form, + error="Tous les comptes disponibles sur cette instance ont deja été distribués." + ) + except NextcloudApiException: + return form_get( + data=request.form, + error="Tous les comptes disponibles sur cette instance ont deja été distribués." + ) + + try: api.create_account( username=request.form.get("username", "").strip(),