From 738596a13f4e60c2265fbbd43f5887209b3f7457 Mon Sep 17 00:00:00 2001
From: Doug Le Tough
Date: Tue, 27 Feb 2018 19:51:43 +0100
Subject: [PATCH] "Base"
---
participate.py | 342 +++++++++++++++++++++++++-----
tetawebapp.sql => participate.sql | 43 +++-
static/scripts/participate.js | 20 ++
static/styles/tetawebapp.css | 48 ++++-
templates/account.html | 14 +-
templates/account_by_id.html | 15 ++
templates/index.html | 9 +-
templates/list_users.html | 29 +++
templates/login_or_register.html | 6 +-
9 files changed, 461 insertions(+), 65 deletions(-)
rename tetawebapp.sql => participate.sql (69%)
create mode 100644 templates/account_by_id.html
create mode 100644 templates/list_users.html
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
-
+
+ Mot de passe oublié ? Envoyez une demande de réinitialisation de votre mot de passe.
+