Last active
December 22, 2015 18:49
-
-
Save adharris/6515993 to your computer and use it in GitHub Desktop.
Simple Flask app using Drupal Bakery for authentication
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
venv | |
templates | |
*.pyc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from flask import Flask, request, redirect, url_for, render_template | |
from flask.ext.sqlalchemy import SQLAlchemy | |
from flask.ext.login import LoginManager, current_user, login_user | |
from bakery import * | |
# Config | |
SQLALCHEMY_DATABASE_URI = 'postgres:///python_bakery' | |
SECRET_KEY = "flask key" | |
SERVER_NAME = "flask.drupal.local" | |
BAKERY_KEY = "strong-and-secret-key" | |
BAKERY_MASTER = "drupal.local" | |
BAKERY_COOKIE_DOMAIN = ".drupal.local" | |
BAKERY_COOKIE_AGE = 60 * 60 * 24 # 1 day | |
app = Flask(__name__) | |
# Use the current file as the flask config | |
app.config.from_object(__name__) | |
db = SQLAlchemy(app) | |
login_manager = LoginManager(app) | |
class User(db.Model): | |
id = db.Column(db.Integer, primary_key=True) | |
username = db.Column(db.String, unique=True) | |
email = db.Column(db.String, unique=True) | |
init = db.Column(db.String, unique=True) | |
# The following 4 methods are required by Flask-Login | |
def is_authenticated(self): | |
return True | |
def is_active(self): | |
return True | |
def is_anonymous(self): | |
return False | |
def get_id(self): | |
return unicode(self.id) | |
# Tell Flask-Login how to load a given user by id. | |
@login_manager.user_loader | |
def load_user(id): | |
return User.query.get(id) | |
# Simpe view to show authentication status: | |
@app.route("/") | |
def home(): | |
if current_user.is_authenticated(): | |
return "Authenticated as {0}".format(current_user.username) | |
else: | |
return "Not Authenticated" | |
@app.route("/login", methods=['GET', 'POST']) | |
def login(): | |
# Get the Decrypted user information from the shared cookie, | |
# if there is no cookie, or the signature doesnt' match, we'll | |
# get None. | |
cookie = bakery_decrypt(request.cookies.get("CHOCOLATECHIP")) | |
if cookie: | |
# Lookup the user by the init url | |
user = User.query.filter(User.init == cookie['init']).first() | |
# If there is no user in the database, then we need to create | |
# one first | |
if not user: | |
user = User(username=cookie['name'], | |
email=cookie['mail'], | |
init=cookie['init']) | |
db.session.add(user) | |
db.session.commit() | |
# Log in the user | |
login_user(user) | |
# Redirect to home | |
return redirect(url_for('home')) | |
if request.method == 'POST': | |
# The login form was submitted, we can procced with bakery login | |
return bakery_login( | |
request.form.get('username'), | |
request.form.get('password'), | |
request.values.get('next'), | |
) | |
return render_template('login.html') | |
@app.route("/bakery/login") | |
def bakery_return(): | |
"""Process the response from the bakery login.""" | |
cookie = bakery_decrypt(request.cookies.get("OATMEAL")) | |
errors = cookie.get('data', {}).get('errors') | |
if errors: | |
# Some error occured, most likely username/password was incorrect. | |
# You'll wanna do something here. | |
pass | |
next = cookie.get('data', {}).get('destination') | |
return redirect(url_for('login', next=next)) | |
if __name__ == '__main__': | |
# Make sure that the database tables are created. | |
db.create_all() | |
app.run(debug=True, port=6000) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import base64, urllib, hmac, hashlib, time | |
import phpserialize | |
from Crypto.Cipher import AES | |
from flask import current_app, make_response, redirect, url_for | |
def bakery_decrypt(cookie): | |
"""Decypt a bakery cookie""" | |
key = current_app.config['BAKERY_KEY'] | |
if cookie is None: | |
return None | |
data = urllib.unquote(cookie.encode('ascii')).decode("utf-8") | |
data = base64.b64decode(data) | |
signature = data[0:64] | |
encrypted = data[64:] | |
token = hmac.new(key, encrypted, hashlib.sha256).hexdigest() | |
if token != signature: | |
return None | |
key_length = max(16, len(key) - len(key) % 8 + 8) | |
padded_key = key.ljust(key_length, '\0') | |
decryptor = AES.new(padded_key, AES.MODE_ECB) | |
decrypted_data = decryptor.decrypt(encrypted) | |
return phpserialize.loads(decrypted_data) | |
def bakery_encrypt(data): | |
"""Encrypts a dictionary data as a bakery cookie""" | |
key = current_app.config['BAKERY_KEY'] | |
key_length = max(16, len(key) - len(key) % 8 + 8) | |
padded_key = key.ljust(key_length, '\0') | |
serialized = phpserialize.dumps(data) | |
serialized = serialized + ('\0' * (16 - len(serialized) % 16)) | |
encryptor = AES.new(padded_key, AES.MODE_ECB) | |
encrypted_data = encryptor.encrypt(serialized) | |
signature = hmac.new(key, encrypted_data, hashlib.sha256).hexdigest().strip() | |
cookie = base64.b64encode(signature + encrypted_data) | |
return urllib.quote(cookie.encode('utf-8')).decode("ascii") | |
def bakery_login(name, password, destination): | |
"""Log in using bakery""" | |
cookie = { | |
'data': { | |
'name': name, | |
'pass': password, | |
'op' : 'Log in', | |
'destination' : destination, | |
}, | |
'name': name, | |
'type': 'OATMEAL', | |
'master': 0, | |
'slave': "http://" + current_app.config['SERVER_NAME'], | |
'calories': 320, | |
'timestamp': int(time.time()), | |
} | |
data = bakery_encrypt(cookie) | |
master_login = '//{0}/bakery/login'.format(current_app.config['BAKERY_MASTER']) | |
response = make_response(redirect(master_login)) | |
set_cookie(response, "OATMEAL", data) | |
return response | |
def set_cookie(response, name, cookie): | |
"""Sets a bakery cookie on the given response""" | |
response.set_cookie(name, cookie, | |
max_age=current_app.config['BAKERY_COOKIE_AGE'], | |
domain=current_app.config['BAKERY_COOKIE_DOMAIN']) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<form method="POST"> | |
<label>Username</label> <input type="text" name="username"> <br> | |
<label>Password</label> <input type="password" name="password"> <br> | |
<input type="submit" value="login"> | |
</form> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment