diff --git a/participate.py b/participate.py index 74e11a9..8014bdd 100755 --- a/participate.py +++ b/participate.py @@ -41,6 +41,7 @@ class Tetawebapp_users(db.Model): name = db.Column(db.Text, nullable=True) phone = db.Column(db.Text, nullable=True) diet = db.Column(db.Text, nullable=True) + is_admin = db.Column(db.Integer, nullable=False, default=0) class Tetawebapp_roles(db.Model): __tablename__ = 'participer_thsf_roles' @@ -48,6 +49,15 @@ class Tetawebapp_roles(db.Model): role = db.Column(db.Text, nullable=False) description = db.Column(db.Text, nullable=False) +class Tetawebapp_turns(db.Model): + __tablename__ = 'participer_thsf_turns' + id = db.Column(db.Integer, primary_key=True) + role_id = db.Column(db.Integer, db.ForeignKey('participer_thsf_roles.id'), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('participer_thsf_users.id'), nullable=True) + wday = db.Column(db.Enum('J', 'V', 'S', 'D'), nullable=False) + start_time = db.Column(db.Time, nullable=False) + end_time = db.Column(db.Time, nullable=False) + ######################################################################## # Menu and navigation management ######################################################################## @@ -62,14 +72,17 @@ def get_menu(page): - The int 0 is used to determine which menu entry is actally called. The value MUST be 0.""" menu = [[u'Accueil', {u'/': [u'/']}, 0], - [u'Mon compte', {u'/account': [u'/account', u'/account/']}, 0], - [u'Mes quarts', {u'/turn': [u'/turn']}, 0], - [u'Feuille de staff', {u'/staff_sheet': [u'/staff_sheet']}, 0], + [u'Mon compte', {u'/account': [u'/account', u'/account/update']}, 0], + [u'Mes tours de staff', {u'/turn': [u'/turn']}, 0], + [u'Feuilles de staff', {u'/staff_sheets': [u'/staff_sheet']}, 0], + [u'Déconnexion', {u'/logout': [u'/logout']}, 0], + ] + if session['is_admin']: + menu = [[u'Accueil', {u'/': [u'/']}, 0], + [u'Tours de staff', {u'/staff': [u'/staff']}, 0], + [u'Feuilles de staff', {u'/staff_sheets': [u'/staff_sheet']}, 0], + [u'Liste des staffers', {u'/users': [u'/users', u'/account/']}, 0], [u'Déconnexion', {u'/logout': [u'/logout']}, 0], - [u'Inputs', {u'/inputs': [u'/inputs']}, 0], - [u'Ajax', {u'/ajax': [u'/ajax']}, 0], - [u'Database', {u'/database': [u'/database']}, 0], - [u'Todo', {u'/todo': [u'/todo']}, 0], ] for item in menu: for url in item[1]: @@ -122,29 +135,153 @@ def check_session(func): def check(*args, **kwargs): try: if session['token'] == request.cookies['token'] and len(session['token']) > 0: + # User is logged in and identified return func(*args, **kwargs) else: + # User is not logged in or session expired session['token'] = '' response = app.make_response(render_template('login_or_register.html', message='')) sync_cookies(response, session) return response except KeyError: + # User is not logged in return render_template('login_or_register.html', message='') return check def check_login(login, password): """ Puts the login verification code here """ - password = password.encode('utf-8') hashed_password = bcrypt.hashpw(password, bcrypt.gensalt()) stored_hash = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.password).first() + is_admin = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.is_admin).first() if stored_hash: if bcrypt.checkpw(password, stored_hash[0].encode('utf-8')): + session['is_admin'] = is_admin[0] return True return False +def register_user(login, password, confirm): + """ Register new user """ + if password != confirm: + # Password does not match confirmation + print "[+] Password mismatch confirmation" + return False + check_user = Tetawebapp_users.query.filter_by(mail=login).count() + if check_user != 0: + # User already exists + print "[+] User already exists" + return False + hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()) + user = Tetawebapp_users(mail=login.encode('utf8'), password=hashed_password) + try: + db.session.add(user) + commit = db.session.commit() + except Exception as e: + db.session.rollback() + print "[+] Error at register_user:" + print "------------------------------" + print "%s" % e.message + print "------------------------------" + return False + if commit != None: + return False + return True + +def update_user(login, password, confirm, name, phone, diet): + """ Update user infos with provided data """ + if password != confirm: + # Password does not match confirmation + print "[+] Password mismatch confirmation" + return False + check_user = Tetawebapp_users.query.filter_by(mail=login).count() + if check_user == 0: + # User does not exist + print "[+] User does not exist" + return False + user = Tetawebapp_users.query.filter_by(mail=login).first() + if len(password) > 0: + # User requested password modification + hashed_password = bcrypt.hashpw(password, bcrypt.gensalt()) + setattr(user, 'password', hashed_password) + # Password has been updated if necessary + # Now let's update other data + setattr(user, 'name', name) + setattr(user, 'phone', phone) + setattr(user, 'diet', diet) + try: + db.session.add(user) + commit = db.session.commit() + except Exception as e: + db.session.rollback() + print "[+] Error at update_user:" + print "------------------------------" + print "%s" % e.message + print "------------------------------" + return False + if commit != None: + return False + return True + +def update_user_by_id(user_id, login, password, confirm, name, phone, diet): + """ Update user infos with provided data """ + if password != confirm: + # Password does not match confirmation + print "[+] Password mismatch confirmation" + return False + check_user = Tetawebapp_users.query.filter_by(id=user_id).count() + if check_user == 0: + # User does not exist + print "[+] User does not exist" + return False + user = Tetawebapp_users.query.filter_by(id=user_id).first() + if len(password) > 0: + # User requested password modification + hashed_password = bcrypt.hashpw(password, bcrypt.gensalt()) + setattr(user, 'password', hashed_password) + # Password has been updated if necessary + # Now let's update other data + setattr(user, 'name', name) + setattr(user, 'phone', phone) + setattr(user, 'diet', diet) + try: + db.session.add(user) + commit = db.session.commit() + except Exception as e: + db.session.rollback() + print "[+] Error at update_user:" + print "------------------------------" + print "%s" % e.message + print "------------------------------" + return False + if commit != None: + return False + return True + +def delete_user(user_id): + """ Delete user """ + try: + Tetawebapp_users.query.filter_by(id=int(user_id)).delete() + db.session.commit() + return True + except ValueError as e: + return False + except Exception as e: + db.session.rollback() + print "[+] Error at delete_user:" + print "------------------------------" + print "%s" % e.message + print "------------------------------" + return False + def check_user_info(): """ Check user info and send appropriate message if info are not complete""" - message = "Vos informations personnelles ne sont pas totalement renseignées. N'oubliez pas de remplir votre fiche située dans la section 'Mon compte'" + message = '' + user = Tetawebapp_users.query.filter_by(mail=session['login']).first() + name = user.name + phone = user.phone + diet = user.diet + if name == None or phone == None or diet == None or \ + len(name) == 0 or len(phone) == 0 or len(diet) == 0: + message = "Vos informations personnelles ne sont pas complètement renseignées. N'oubliez pas de remplir votre fiche située dans la section 'Mon compte'" return message.decode('utf-8') def gen_token(): @@ -165,44 +302,55 @@ def page_not_found(e): @app.route("/login", methods=['GET', 'POST']) def login(): - login = request.form.get('login') - password = request.form.get('password') - if check_login(login, password): - # Generate and store a token in session - session['token'] = gen_token() - # Return user to index page - page = '/' - menu = get_menu(page) - message = check_user_info() - response = app.make_response(render_template('index.html', menu=menu, message=message)) - # Push token to cookie + try: + login = request.form.get('login').encode('utf-8') + password = request.form.get('password').encode('utf-8') + if check_login(login, password): + # Generate and store a token in session + session['token'] = gen_token() + session['login'] = login + # Return user to index page + page = '/' + menu = get_menu(page) + message = check_user_info() + response = app.make_response(render_template('index.html', menu=menu, message=message, login=login)) + # Push token to cookie + sync_cookies(response, session) + return response + # Credentials are not valid + response = app.make_response(render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")) + session['token'] = '' sync_cookies(response, session) return response - # Credentials are not valid - response = app.make_response(render_template('login_or_register.html', message='Invalid user or password')) - session['token'] = '' - sync_cookies(response, session) - return response + except AttributeError: + return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide") @app.route("/register", methods=['GET', 'POST']) def register(): - login = request.form.get('login') - password = request.form.get('password') - if check_login(login, password): - # Generate and store a token in session - session['token'] = gen_token() - # Return user to index page - page = '/' - menu = get_menu(page) - response = app.make_response(render_template('index.html', menu=menu)) - # Push token to cookie + try: + login = request.form.get('login').encode('utf-8') + password = request.form.get('password').encode('utf-8') + confirm = request.form.get('confirm').encode('utf-8') + if register_user(login, password, confirm): + # Generate and store a token in session + session['token'] = gen_token() + session['login'] = login + # Return user to index page + page = '/' + menu = get_menu(page) + message = check_user_info() + response = app.make_response(render_template('index.html', menu=menu, login=login, message=message)) + # Push token to cookie + sync_cookies(response, session) + return response + # Error while registering user + message = "Erreur lors de l'enregsitrement: L'utilisateur existe t-il déjà ?".decode('utf-8') + response = app.make_response(render_template('login_or_register.html', message=message)) + session['token'] = '' sync_cookies(response, session) return response - # Credentials are not valid - response = app.make_response(render_template('login_or_register.html', message='Invalid user or password')) - session['token'] = '' - sync_cookies(response, session) - return response + except AttributeError: + return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide") @app.route("/", methods=['GET', 'POST']) @check_session @@ -211,25 +359,122 @@ def index(): page = str(request.url_rule) menu = get_menu(page) message = check_user_info() - return render_template('index.html', menu=menu, message=message) + return render_template('index.html', menu=menu, message=message, login=session['login']) @app.route("/account", methods=['GET', 'POST']) @check_session def account(): - """ Arcticles page """ + """ Account page """ page = str(request.url_rule) menu = get_menu(page) - return render_template('account.html', menu=menu) + user = Tetawebapp_users.query.filter_by(mail=session['login']).first() + mail = '' if user.mail == None else user.mail + name = '' if user.name == None else user.name + phone = '' if user.phone == None else user.phone + diet = '' if user.diet == None else user.diet + message = check_user_info() + return render_template('account.html', menu=menu, mail=mail, name=name, phone=phone, diet=diet, message=message) + +@app.route("/account/update", methods=['GET', 'POST']) +@check_session +def update_account(): + """ Update current account """ + try: + page = str(request.url_rule) + menu = get_menu(page) + login = session['login'] + password = request.form.get('password').encode('utf-8') + confirm = request.form.get('confirm').encode('utf-8') + name = request.form.get('name').encode('utf-8') + phone = request.form.get('phone').encode('utf-8') + diet = request.form.get('diet').encode('utf-8') + if update_user(login, password, confirm, name, phone, diet): + message = check_user_info() + else: + message = "Erreur lors de l'enregistrement des données." + return render_template('account.html', + menu=menu, + mail=login.decode('utf-8'), + name=name.decode('utf-8'), + phone=phone.decode('utf-8'), + diet=diet.decode('utf-8'), + message=message) + except AttributeError: + return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide") + +@app.route("/users", methods=['GET', 'POST']) +@check_session +def list_users(): + """ Users list """ + page = str(request.url_rule) + menu = get_menu(page) + message = check_user_info() + staffers = Tetawebapp_users.query.filter_by(is_admin=0).order_by(Tetawebapp_users.name).all() + return render_template('list_users.html', menu=menu, staffers=staffers, message=message) @app.route("/account/", methods=['GET', 'POST']) @check_session def account_by_id(ID): """ Arcticles page """ - page = str(request.url_rule) - menu = get_menu(page) - selected = page.replace('', ID) - navbar = get_navbar(page, selected) - return render_template('account_by_id.html', menu=menu, navbar=navbar, ID=ID) + try: + if session['is_admin']: + page = str(request.url_rule) + menu = get_menu(page) + user = Tetawebapp_users.query.filter_by(id=ID).first() + return render_template('account_by_id.html', menu=menu, user=user) + # User is not admin + return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide") + except AttributeError: + # User is not logged in + return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide") + +@app.route("/account/update/", methods=['GET', 'POST']) +@check_session +def update_account_by_id(ID): + """ Update given account """ + try: + if session['is_admin']: + page = str(request.url_rule) + menu = get_menu(page) + login = session['login'] + password = request.form.get('password').encode('utf-8') + confirm = request.form.get('confirm').encode('utf-8') + name = request.form.get('name').encode('utf-8') + phone = request.form.get('phone').encode('utf-8') + diet = request.form.get('diet').encode('utf-8') + if update_user_by_id(ID, login, password, confirm, name, phone, diet): + user = Tetawebapp_users.query.filter_by(id=ID).first() + message = check_user_info() + else: + message = "Erreur lors de l'enregistrement des données." + return render_template('account_by_id.html', + menu=menu, + user=user, + message=message) + # User is not admin + return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide") + except AttributeError: + # User is not logged in + return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide") + +@app.route("/account/delete/", methods=['GET', 'POST']) +@check_session +def delete_account(ID): + """ Delete given account """ + try: + if session['is_admin']: + message = "Erreur lors de la suppression.".decode('utf-8') + if delete_user(ID): + message = '' + page = str(request.url_rule) + menu = get_menu(page) + staffers = Tetawebapp_users.query.filter_by(is_admin=0).order_by(Tetawebapp_users.name).all() + return render_template('list_users.html', menu=menu, staffers=staffers, message=message) + # User is not admin + return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide") + except AttributeError: + # User is not logged in + return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide") @app.route("/logout", methods=['GET', 'POST']) @check_session @@ -237,6 +482,7 @@ def logout(): """ Logout user """ # Remove session token session['token'] = None + session['login'] = None # Return user to index page response = app.make_response(render_template('login_or_register.html', message='')) # Push token to cookie diff --git a/tetawebapp.sql b/participate.sql similarity index 69% rename from tetawebapp.sql rename to participate.sql index 126394d..349268c 100644 --- a/tetawebapp.sql +++ b/participate.sql @@ -36,25 +36,64 @@ CREATE TABLE participer_thsf_users ( password text not NULL, name text, phone text, - diet text + diet text, + is_admin integer not NULL ); +\echo ********************************* +\echo * Creating participer_thsf_roles table +\echo ********************************* + CREATE TABLE participer_thsf_roles ( id serial primary key, role text not NULL, description text not NULL ); +\echo ********************************* +\echo * Creating participer_thsf_turns table +\echo ********************************* +CREATE TYPE dow AS ENUM ('J', 'V', 'S', 'D'); +CREATE TABLE participer_thsf_turns ( + id serial primary key, + role_id integer not NULL, + user_id integer, + wday dow not NULL, + start_time time not NULL, + end_time time not NULL, + constraint fk_turns_role + foreign key (role_id) + REFERENCES participer_thsf_roles (id), + constraint fk_turns_user + foreign key (user_id) + REFERENCES participer_thsf_users (id) +); + \echo ************************************************* \echo * Giving participer_thsf_users ownership to participer_thsf \echo ************************************************* alter table participer_thsf_users owner to participer_thsf; +\echo ************************************************* +\echo * Giving participer_thsf_roles ownership to participer_thsf +\echo ************************************************* +alter table participer_thsf_roles owner to participer_thsf; + +\echo ************************************************* +\echo * Giving participer_thsf_turns ownership to participer_thsf +\echo ************************************************* +alter table participer_thsf_turns owner to participer_thsf; + \echo ********************************************************************* \echo * Inserting user demo identified by password demo to participer_thsf_users \echo ********************************************************************* -insert into participer_thsf_users (mail, password, name) values ('demo', '$2b$12$yjv4QMctGJFj2HmmbF6u5uDq9ATIl/Y9Z96MbaqRrcG6AE0CGHKSS', 'demo'); +insert into participer_thsf_users (mail, password, name, phone, diet, is_admin) values ('bofh@tetalab.org', + '$2b$12$wm7PQ9IE7TYGYk1XB11mPusq7HsjYLYxt5G4v5Wz.jZbh5iWDHP5q', + 'BOFH', + '0000000000', + 'Omnivore', + 1); \echo ********************************************************************* \echo * Inserting roles to participer_thsf_roles diff --git a/static/scripts/participate.js b/static/scripts/participate.js index 36ac4b3..ab6a665 100644 --- a/static/scripts/participate.js +++ b/static/scripts/participate.js @@ -17,3 +17,23 @@ function register() { } return true; } + +function update_account() { + var password = document.getElementById('password').value; + var confirm = document.getElementById('confirm').value; + if (password != confirm){ + alert("Confirmation mot de passe incohérente"); + return false; + } + if (password.length > 0 && password.length < 8){ + alert("Le mot de passe doit avoir une longueur d'au moins 8 caractères"); + return false; + } + return true; +} + +function delete_account(id) { + if (confirm("La suppression d'un compte est définitive.\n\nConfirmer ?")) { + document.location='/account/delete/'+id; + } +} diff --git a/static/styles/tetawebapp.css b/static/styles/tetawebapp.css index 2c0fd18..41a690a 100644 --- a/static/styles/tetawebapp.css +++ b/static/styles/tetawebapp.css @@ -370,7 +370,7 @@ input.upload { } form { - width: 350px; + width: 400px; text-align: center; line-height: 40px; } @@ -382,3 +382,49 @@ form > label { form > input[type='text'], form > input[type='password'] { float: right; } + +div.table_header { + background-color: var(--coloured-bg); + text-align: left; + width: 910px; +} + +div.table_row { + text-align: left; + width: 910px; +} + +div.table_header > div, div.table_row > div { + display: inline-block; + text-align: center; + font-weight: bold; + width: 200px; +} + +div.table_row > div { + border-color: var(--coloured-bg); + border-width: 1px; + font-weight: normal; +} + +div.even { + background-color: var(--light-coloured-bg); + border-color: var(--coloured-bg); + border-width: 1px; + border-style: none none solid none; +} + +div.odd { + background-color: var(--white); + border-color: var(--coloured-bg); + border-width: 1px; + border-style: none none solid none; +} + +div.table_row > div.border_left { + border-style: none none none solid; +} + +div.table_row > div.border_right { + border-style: none solid none none; +} diff --git a/templates/account.html b/templates/account.html index ab78428..90f9935 100644 --- a/templates/account.html +++ b/templates/account.html @@ -16,13 +16,13 @@
  • Aucune des données que vous nous transmettrez ne sera fournie à un tiers
  • -
    -
    -
    -
    -
    -
    -
    + +
    +
    +
    +
    +
    +
    {% endblock %} diff --git a/templates/account_by_id.html b/templates/account_by_id.html new file mode 100644 index 0000000..e878ec2 --- /dev/null +++ b/templates/account_by_id.html @@ -0,0 +1,15 @@ +{% extends "index.html" %} +{% block title %}Articles{% endblock %} + {% block main %} +
    +

    Informations du compte

    + +
    +
    +
    +
    +
    +
    + +
    + {% endblock %} diff --git a/templates/index.html b/templates/index.html index fdbb867..fd1450e 100644 --- a/templates/index.html +++ b/templates/index.html @@ -46,12 +46,13 @@ {% endif %} + {% if message and message != '' %} +
    {{ message }}
    + {% endif %} {% block main %}
    - {% if message != '' %} -
    {{ message }}
    - {% endif %}

    We Make THSF

    +

    Bonjour {{ login }},

    Comme chaque année le Toulouse Hacker Space Factory aura lieu à Mix'Art Myrys. @@ -71,7 +72,7 @@ de sélectionner le poste et les créneaux horaires pendant lesquels vous souhaitez vous rendre disponible.

  • - d'être tenu informé des évolutions de l'organisation et du déroulement du THSF via la mailing list du staff. + de poser vos questions, obtenir des réponses et être tenu informé des évolutions de l'organisation et du déroulement du THSF via la mailing list du staff.
  • diff --git a/templates/list_users.html b/templates/list_users.html new file mode 100644 index 0000000..741d9fe --- /dev/null +++ b/templates/list_users.html @@ -0,0 +1,29 @@ +{% extends "index.html" %} +{% block title %}Articles{% endblock %} + {% block main %} +
    +

    Liste des staffers enregistrés

    +
    +
    ID
    +
    Mail
    +
    Nom
    +
    Téléphone
    +
    Régime / Remarques
    +
    Action
    +
    + {% set row_class = cycler('odd', 'even') %} + {% for staffer in staffers %} +
    +
    {{ staffer.id }}
    +
    {{ staffer.mail }}
    +
    {{ staffer.name }}
    +
    {{ staffer.phone }}
    +
    {{ staffer.diet }}
    +
    + + +
    +
    + {% endfor %} +
    + {% endblock %} diff --git a/templates/login_or_register.html b/templates/login_or_register.html index af08004..ea64e70 100644 --- a/templates/login_or_register.html +++ b/templates/login_or_register.html @@ -40,9 +40,6 @@

    Aucun électron n'a été maltraité lors de la mise au point de ce site. Par ailleurs ce site n'utilise ni ressources hébergées par des tiers, ni bullshitwares, ni trackers.

    - {% if message != '' %} -
    {{ message }}
    - {% endif %}