Skip to content

Instantly share code, notes, and snippets.

@adharris
Last active December 22, 2015 18:49
Show Gist options
  • Save adharris/6515993 to your computer and use it in GitHub Desktop.
Save adharris/6515993 to your computer and use it in GitHub Desktop.
Simple Flask app using Drupal Bakery for authentication
venv
templates
*.pyc
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)
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'])
<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