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(),