#!/usr/bin/env python # -*- coding: utf-8 # Required modules import os import inspect import random import binascii import bcrypt from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash from functools import wraps # Optionnal modules import psycopg2 from flask_sqlalchemy import SQLAlchemy ######################################################################## # App settings ######################################################################## app = Flask(__name__) # Path to static files app.static_url_path='/static' # Set debug mode to False for production app.debug = True # Various configuration settings belong here (optionnal) app.config.from_pyfile('config.local.py') # Generate a new key: head -n 40 /dev/urandom | md5sum | cut -d' ' -f1 app.secret_key = 'ce1d1c9ff0ff388a838b3a1e3207dd27' # Feel free to use SQLAlchemy (or not) db = SQLAlchemy(app) ######################################################################## # Sample user database ######################################################################## class Tetawebapp_users(db.Model): __tablename__ = 'tetawebapp_users' id = db.Column(db.Integer, primary_key=True) mail = db.Column(db.Text, nullable=False) password = db.Column(db.Text, nullable=False) name = db.Column(db.Text, nullable=False) ######################################################################## # Menu and navigation management ######################################################################## def get_menu(page): """ The main menu is a list of lists in the followin format: [unicode caption, {unicode URL endpoint: [unicode route, ...]}, int 0] - The URL end point is the URL where to point to (href) - One of the routes MUST match the route called by request - The int 0 is used to determine which menu entry is actally called. The value MUST be 0.""" menu = [[u'Home', {u'/': [u'/']}, 0], [u'Articles', {u'/articles': [u'/articles', u'/articles/']}, 0], [u'Basics', {u'/basics': [u'/basics']}, 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]: for route in item[1][url]: if route == page: item[2] = 1 return menu # This should never happen return menu def get_navbar(page, selected): """ The horizontal navbar is a list of lists in the followin format: [unicode caption, {unicode URL endpoint: [unicode route, ...]}, int 0] - The URL end point is the URL where to point to (href) - One of the routes MUST match the route called by request - The int 0 is used to de """ navbars = [[u'First article', {u'/articles/1': [u'/articles', u'/articles/']}, 0, 0], [u'Second article', {u'/articles/2': [u'/articles', u'/articles/']}, 0, 0], [u'Third article', {u'/articles/3': [u'/articles', u'/articles/']}, 0, 0] ] navbar = [] for item in navbars: for url in item[1]: if url == selected: item[2] = 1 for route in item[1][url]: if route == page: navbar.append(item) navbar[len(navbar) - 1][3] = 1 return navbar ######################################################################## # Session management ######################################################################## def sync_session(request, session): """ Synchronize cookies with session """ for key in request.cookies: session[key] = request.cookies[key].encode('utf8') def sync_cookies(response, session): """ Synchronize session with cookies """ for key in session: response.set_cookie(key, value=str(session[key])) def check_session(func): """ Check if the session has required token cookie set. If not, redirects to the login page. """ @wraps(func) def check(*args, **kwargs): try: if session['token'] == request.cookies['token'] and len(session['token']) > 0: return func(*args, **kwargs) else: session['token'] = '' response = app.make_response(render_template('login.html', message='')) sync_cookies(response, session) return response except KeyError: return render_template('login.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() if stored_hash: if bcrypt.checkpw(password, stored_hash[0].encode('utf-8')): return True return False def gen_token(): """ Generate a random token to be stored in session and cookie """ token = binascii.hexlify(os.urandom(42)) return token ######################################################################## # Routes: # ------- # Except for the index function, the function name MUST have the same # name than the URL endpoint to make the menu work properly ######################################################################## @app.errorhandler(404) def page_not_found(e): """ 404 not found """ return render_template('error.html'), 404 @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) response = app.make_response(render_template('index.html', menu=menu)) # Push token to cookie sync_cookies(response, session) return response # Credentials are not valid response = app.make_response(render_template('login.html', message='Invalid user or password')) session['token'] = '' sync_cookies(response, session) return response @app.route("/", methods=['GET', 'POST']) @check_session def index(): """ Index page """ page = str(request.url_rule) menu = get_menu(page) return render_template('index.html', menu=menu) @app.route("/articles", methods=['GET', 'POST']) @check_session def articles(): """ Arcticles page """ page = str(request.url_rule) menu = get_menu(page) navbar = get_navbar(page, '') return render_template('articles.html', menu=menu, navbar=navbar) @app.route("/articles/", methods=['GET', 'POST']) @check_session def articles_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('articles_by_id.html', menu=menu, navbar=navbar, ID=ID) @app.route("/basics", methods=['GET', 'POST']) @check_session def basics(): """ Basics page """ page = str(request.url_rule) menu = get_menu(page) return render_template('basics.html', menu=menu) @app.route("/inputs", methods=['GET', 'POST']) @check_session def inputs(): """ Show the input collection """ page = str(request.url_rule) menu = get_menu(page) return render_template('inputs.html', menu=menu) @app.route("/ajax", methods=['GET', 'POST']) @check_session def ajax(): """ Propose various AJAX tests """ page = str(request.url_rule) menu = get_menu(page) return render_template('ajax.html', menu=menu) @app.route("/database", methods=['GET', 'POST']) @check_session def database(): """ A blah on using databases """ page = str(request.url_rule) menu = get_menu(page) return render_template('database.html', menu=menu) @app.route("/todo", methods=['GET', 'POST']) @check_session def todo(): """ The famous TODO list """ page = str(request.url_rule) menu = get_menu(page) return render_template('todo.html', menu=menu) ######################################################################## # AJAX routes ######################################################################## @app.route("/get_html_from_ajax", methods=['GET', 'POST']) @check_session def get_html_from_ajax(): """ Return HTML code to an AJAX request It may generate a 404 http error for testing purpose """ if int(random.random()*10) % 2: # Randomly generate 404 HTTP response return render_template('error.html'), 404 return render_template('ajax_html.html') @app.route("/get_value_from_ajax", methods=['GET', 'POST']) @check_session def get_value_from_ajax(): """ Return a randomly generated value to an AJAX request It may return an error code for testing purpose """ err_code = 'TETA_ERR' RND = int(random.random()*10) if RND % 2: # Randomly generate error return err_code return str(RND) @app.route("/set_value_from_ajax/", methods=['GET', 'POST']) @check_session def set_value_from_ajax(value): """ Accept a value from an AJAX request It may return an error code for testing purpose """ err_code = 'TETA_ERR' if value != 'We Make Porn': return 'True' return err_code @app.route("/upload", methods=['POST']) @check_session def upload(): """ Save a file from AJAX request Files are saved in UPLOADED_FILES_DEST (see config.local.py) """ err_code = 'TETA_ERR' RND = int(random.random()*10) if RND % 2: # Randomly generate error print err_code return err_code uploaded_files = [] if len(request.files) > 0 and request.files['files']: uploaded_files = request.files.getlist("files") print "Uploaded files:" for f in uploaded_files: print ' [+] %s [%s]' % (f.filename, f.content_type) # Before saving you should: # - Secure the filename # - Check file size # - Check content type f.save(os.path.join(app.config['UPLOADED_FILES_DEST'], f.filename)) f.close() return "OK" ######################################################################## # Main ######################################################################## if __name__ == '__main__': app.run(host='0.0.0.0')