Browse Source

Tighten up POSTing

* json will do basic authentication -- not sophisticated, but works
* regular request will redirect to login page
* csrf token
* /movies/<option> instead of <lang>
master
sipp11 10 years ago
parent
commit
eb436a459b
  1. 54
      auth.py
  2. 106
      flasky.py
  3. 3
      settings.py
  4. 4
      site.default.cfg
  5. 9
      templates/base.html
  6. 17
      templates/layout.html
  7. 15
      templates/login.html

54
auth.py

@ -0,0 +1,54 @@
from functools import wraps
from flask import (
request, Response, session, flash, redirect, url_for, abort
)
from settings import app_password, app_user
import random
import string
def csrf_token_generator(size=40, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
def check_basic_auth(user, passwd):
if user != app_user or passwd != app_password:
return False
else:
return True
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'}
)
def requires_auth(f):
'''
REQUEST.json
only gets basic authentication
REQUEST.get
redirect to login page
'''
@wraps(f)
def decorated(*args, **kwargs):
if request.json:
auth = request.headers.get('Authorization')
if auth.startswith('Basic'):
basic_auth = request.authorization
if not check_basic_auth(basic_auth.username, basic_auth.password):
abort(401)
else:
abort(401)
return f(*args, **kwargs)
auth = session.get('logged_in')
if not auth:
flash('You are not authorized')
return redirect(url_for('hello_world'))
return f(*args, **kwargs)
return decorated

106
flasky.py

@ -1,12 +1,17 @@
from flask import Flask, make_response, request, current_app, abort
# -*- coding: utf-8 -*-
from flask import (
Flask, make_response, request, current_app, abort, session, flash,
redirect, url_for, render_template
)
from simplejson import dumps
from pymongo import MongoClient, DESCENDING # ASCENDING
import datetime
import dateutil.parser
import bson
from settings import mongo_config
from settings import mongo_config, app_password, app_user
from datetime import timedelta
from functools import update_wrapper
from auth import requires_auth, csrf_token_generator
def crossdomain(origin=None, methods=None, headers=None,
@ -51,7 +56,22 @@ def crossdomain(origin=None, methods=None, headers=None,
return decorator
def generate_csrf_token():
if '_csrf_token' not in session:
session['_csrf_token'] = csrf_token_generator()
return session['_csrf_token']
app = Flask(__name__)
# Load default config and override config from an environment variable
app.config.update(dict(
DEBUG=True,
SECRET_KEY='development key',
USERNAME=app_user,
PASSWORD=app_password,
))
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
app.jinja_env.globals['csrf_token'] = generate_csrf_token
client = MongoClient(mongo_config)
db = client.showtimes
@ -61,26 +81,51 @@ miscObjHandler = lambda obj: (
else str(obj) if isinstance(obj, bson.objectid.ObjectId) else None)
@app.before_request
def csrf_protect():
'''
Skip CSRF-token for RESTful service
ref: http://flask.pocoo.org/snippets/3/
'''
if request.method == "POST" and not request.json:
token = session.pop('_csrf_token', None)
if not token or token != request.form.get('_csrf_token'):
abort(403)
@app.route('/')
@app.route('/flask/')
@crossdomain(origin='*')
def hello_world():
return 'This comes from Flask ^_^'
# return 'This comes from Flask ^_^'
return render_template('layout.html')
@app.route('/movies/', methods=['GET'], defaults={'lang': 'en'})
@app.route('/movies/<lang>/', methods=['GET'])
@app.route('/movies/', methods=['GET'], defaults={'option': 'nowshowing'})
@app.route('/movies/<option>/', methods=['GET'])
@crossdomain(origin='*')
def movie_list(lang):
result = db.movies.find().sort('release_date', DESCENDING)
def movie_list(option):
_opt = ('nowshowing', 'comingsoon', 'older')
option = option if option in _opt else 'nowshowing'
query = {}
now = datetime.datetime.now()
today = datetime.datetime(now.year, now.month, now.day)
if option == 'nowshowing':
query['status'] = 'nowshowing'
elif option == 'comingsoon':
query['release_date'] = {'$gte': today}
else:
query['release_date'] = {'$lt': today}
result = db.movies.find(query).sort('release_date', DESCENDING)
movies = []
for i in result:
if 'original' in i['title']:
i['original_title'] = i['title']['original']
i['title'] = i['title'][lang]
i['cast'] = i['cast'][lang]
i['tagline'] = i['tagline'][lang]
i['storyline'] = i['storyline'][lang]
i['director'] = i['director'][lang]
# i['title'] = i['title'][lang]
# i['cast'] = i['cast'][lang]
# i['tagline'] = i['tagline'][lang]
# i['storyline'] = i['storyline'][lang]
# i['director'] = i['director'][lang]
movies.append(i)
json_dict = {
@ -124,6 +169,8 @@ def movie_item(mid, lang):
@app.route('/movie/<mid>/', methods=['GET', 'POST'])
@requires_auth
@crossdomain(origin='*')
def raw_movie_item(mid):
if request.method == 'GET':
try:
@ -187,6 +234,7 @@ def groups():
@app.route('/group/<string:group_id>/', methods=['GET', 'POST'])
@requires_auth
@crossdomain(origin='*')
def group(group_id):
if request.method == 'GET':
@ -230,6 +278,7 @@ def list_theaters(group=None):
@app.route('/theater/<string:theater_id>/', methods=['GET', 'POST'])
@crossdomain(origin='*')
@requires_auth
def get_theater(theater_id):
'''
TODO: cannot add theater yet
@ -294,5 +343,38 @@ def list_showtimes(group=None, code=None):
return r
def check_basic_auth(user, passwd):
if user != app.config['USERNAME'] or \
passwd != app.config['PASSWORD']:
return False
else:
return True
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != app.config['USERNAME']:
error = 'Invalid username'
elif request.form['password'] != app.config['PASSWORD']:
error = 'Invalid password'
else:
session['logged_in'] = True
flash('You were logged in')
return redirect(url_for('hello_world'))
if session.get('logged_in'):
flash('New entry was successfully posted')
return redirect(url_for('hello_world'))
return render_template('login.html', error=error)
@app.route('/logout')
def logout():
session.pop('logged_in', None)
flash('You were logged out')
return redirect(url_for('hello_world'))
if __name__ == '__main__':
app.run(debug=True)

3
settings.py

@ -11,6 +11,9 @@ conf = ConfigParser.RawConfigParser()
conf.read('site.cfg')
port = conf.getint('app', 'port') if conf.has_option('app', 'port') else 8888
app_user = conf.get('app', 'user')
app_password = conf.get('app', 'password')
mongo_conf_data = {
'host': conf.get('mongo', 'host') if conf.has_option('mongo', 'host') else 'localhost',
'port': conf.getint('mongo', 'port') if conf.has_option('mongo', 'port') else 27017,

4
site.default.cfg

@ -1,5 +1,7 @@
[app]
port=8888
port=8989
user=foo
password=bar
[mongo]
host=localhost

9
templates/base.html

@ -1,9 +0,0 @@
<html>
<head>
<title>Tornado Boilerplate</title>
</head>
<body>
<h1>It worked!</h1>
</body>
</html>

17
templates/layout.html

@ -0,0 +1,17 @@
<!doctype html>
<title>Flaskr</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
<div class="page">
<h1>Flaskr</h1>
<div class="metanav">
{% if not session.logged_in %}
<a href="{{ url_for('login') }}">log in</a>
{% else %}
<a href="{{ url_for('logout') }}">log out</a>
{% endif %}
</div>
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% block body %}{% endblock %}
</div>

15
templates/login.html

@ -0,0 +1,15 @@
{% extends "layout.html" %}
{% block body %}
<h2>Login</h2>
{% if error %}<p class="error"><strong>Error:</strong> {{ error }}{% endif %}
<form action="{{ url_for('login') }}" method="post">
<dl>
<dt>Username:
<dd><input type="text" name="username">
<dt>Password:
<dd><input type="password" name="password">
<input name="_csrf_token" type="hidden" value="{{ csrf_token() }}">
<dd><input type="submit" value="Login">
</dl>
</form>
{% endblock %}
Loading…
Cancel
Save