import os from time import sleep from hashlib import pbkdf2_hmac from configparser import ConfigParser from getpass import getpass from binascii import hexlify from datetime import datetime import random from flask import ( Flask, flash, request, render_template, redirect, send_from_directory, session, ) from flask_sqlalchemy import SQLAlchemy CONFIG_PATH = "./config.ini" application = Flask(__name__) if os.path.exists(CONFIG_PATH): config = ConfigParser() config.read(CONFIG_PATH) application.secret_key = config.get('ludo', 'secret_key').encode('utf-8') application.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///ludo.db' db = SQLAlchemy(application) class User(db.Model): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) is_admin = db.Column(db.Boolean, nullable=False, default=False) name = db.Column(db.String(120), unique=True, nullable=False) password = db.Column(db.String(200), nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) class Sub(db.Model): __tablename__ = 'sub' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(80), unique=False, nullable=False) roll_id = db.Column( db.Integer, db.ForeignKey('roll.id'), nullable=False ) roll = db.relationship('Roll', back_populates="subs", foreign_keys=[roll_id]) class Roll(db.Model): __tablename__ = 'roll' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(80), unique=False, nullable=False) roll_datetime = db.Column(db.DateTime, unique=False, nullable=False) subs = db.relationship('Sub', back_populates="roll", foreign_keys=[Sub.roll_id]) winner_id = db.Column(db.Integer, db.ForeignKey('sub.id'), nullable=True) winner = db.relationship('Sub', foreign_keys=[winner_id]) @property def roll_date(self): return self.roll_datetime.strftime('%Y-%m-%d') @property def roll_time(self): return self.roll_datetime.strftime('%H:%M') @property def print_datetime(self): return self.roll_datetime.strftime('%d/%m/%Y à %Hh%M') def create_user(name, password, is_admin, email): salt = hexlify(os.urandom(8)) password_hash = hexlify(pbkdf2_hmac( 'sha256', password.encode('utf-8'), salt, 100_000 )) passwd = f'sha256:100000:{salt.decode("utf-8")}:{password_hash.decode("utf-8")}' user = User(name=name, password=passwd, email=email, is_admin=is_admin) db.session.add(user) return user def check_password(password, db_hash): algo, iter_count, salt, hashed = db_hash.split(":") return hashed == hexlify( pbkdf2_hmac( algo, password.encode('utf-8'), salt.encode('utf-8'), int(iter_count), ) ).decode('utf-8') @application.route("/") def index(): to_roll = Roll.query.filter( Roll.roll_datetime < datetime.now(), Roll.winner == None ) for roll in to_roll: if not roll.subs: Roll.filter_by(id=roll.id).delete() else: winner = random.choice(roll.subs) roll.winner = winner db.session.commit() rolls = Roll.query.order_by(Roll.roll_datetime.desc()).all() return render_template("index.html", rolls=rolls) @application.route("/rolls", methods=['POST']) def rolls(): if session.get("logged", False): clean = True if "roll_date" not in request.form or not request.form['roll_date']: flash('La date de tirage est obligatoire', category='create_roll') clean = False if "roll_time" not in request.form or not request.form['roll_time']: flash('L\'heure de tirage est obligatoire', category='create_roll') clean = False if "name" not in request.form or not request.form['name']: flash('Le nom du tirage est obligatoire', category='create_roll') clean = False if not clean: return redirect('/') name = request.form['name'] try: roll_datetime = datetime.strptime( f"{request.form['roll_date']} {request.form['roll_time']}", "%Y-%m-%d %H:%M" ) except ValueError: flash( 'La date ou l\'heure n\'est pas au bon format', category='create_roll' ) return redirect('/') if roll_datetime < datetime.now(): flash( 'Le date de tirage ne peut pas être dans le passé', category='create_roll' ) return redirect('/') roll = Roll(name=name, roll_datetime=roll_datetime) db.session.add(roll) db.session.commit() return redirect('/') @application.route("/rolls/", methods=['POST']) def rolls_update(roll_id): if session.get("logged", False): roll = Roll.query.filter_by(id=roll_id).one_or_none() clean = True if "roll_date" not in request.form or not request.form['roll_date']: flash('La date de tirage est obligatoire', category=roll_id) clean = False if "roll_time" not in request.form or not request.form['roll_time']: flash('L\'heure de tirage est obligatoire', category=roll_id) clean = False if "name" not in request.form or not request.form['name']: flash('Le nom du tirage est obligatoire', category=roll_id) clean = False if not roll: clean = False if roll and roll.roll_datetime > datetime.now(): flash('Les tirages passé ne sont pas editables', category=roll_id) clean = False if not clean: return redirect('/') name = request.form['name'] try: roll_datetime = datetime.strptime( f"{request.form['roll_date']} {request.form['roll_time']}", "%Y-%m-%d %H:%M" ) except ValueError: flash( 'La date ou l\'heure n\'est pas au bon format', category=roll_id ) return redirect('/') if roll_datetime < datetime.now(): flash( 'Le date de tirage ne peut pas être dans le passé', category=roll_id ) return redirect('/') roll.roll_datetime = roll_datetime roll.name = name db.session.add(roll) db.session.commit() return redirect('/') @application.route("/rolls//subscribe", methods=['POST']) def subscribe(roll_id): if session.get("logged", False): if "name" not in request.form or not request.form['name']: flash('Le nom du participant est obligatoire', category=f'subscribe_{roll_id}') return redirect('/') name = request.form['name'] roll = Roll.query.filter(Roll.id == roll_id).one_or_none() if not roll: return redirect('/') if Sub.query.filter_by(name=name, roll_id=roll_id).one_or_none(): flash('Ce participant est deja inscrit', category=f'subscribe_{roll_id}') elif roll.roll_datetime < datetime.now(): flash('Il est trop tard pour inscrire un participant à ce tirage', category=f'subscribe_{roll_id}') else: sub = Sub(name=name, roll=roll) db.session.add(sub) db.session.commit() return redirect('/') @application.route("/unsubscribe") def unsubscribe(): if session.get("logged", False): if "sub" not in request.args or not request.args['sub']: return redirect('/') sub_id = request.args['sub'] sub = Sub.query.filter_by(id=sub_id).one_or_none() if sub: if not sub.roll.roll_datetime < datetime.now(): Sub.query.filter_by(id=sub_id).delete() db.session.commit() return redirect('/') @application.route("/login", methods=['POST']) def login(): form = request.form user = User.query.filter_by(name=form['login']).one_or_none() if user: if check_password(form['password'], user.password): session['logged'] = True return redirect(form['callback']) sleep(random.random()) return redirect(form['callback']) @application.route("/logout") def logout(): del session['logged'] return redirect(request.args['callback']) @application.route("/style.css") def send_style(): return send_from_directory("static", "style.css") @application.cli.command("setup") def setup(): config = ConfigParser() if os.path.exists(CONFIG_PATH): print('The app is already installed.') config['ludo'] = { 'secret_key': hexlify(os.urandom(32)).decode('utf-8') } db.create_all() name = input("Admin login: ") email = input("Admin email: ") while True: password1 = getpass(prompt='Password: ') password2 = getpass(prompt='Retype your password: ') if password1 == password2: break print("The passwords dont match") create_user( name=name, password=password1, email=email, is_admin=True ) with open(CONFIG_PATH, 'w') as configfile: config.write(configfile) db.session.commit() print('application setup done') @application.cli.command("create_user") def shell_create_user(): name = input("User login: ") email = input("User email: ") while True: password1 = getpass(prompt='Password: ') password2 = getpass(prompt='Retype your password: ') if password1 == password2: break print("The passwords dont match") create_user( name=name, password=password1, email=email, is_admin=False ) db.session.commit() if __name__ == "__main__": application.run(debug=True)