From a81cf3a67c883ae76507ca1242545f0259a0d835 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 2 May 2010 19:07:42 +0200 Subject: [PATCH] Added some openid code. --- flask_website/__init__.py | 3 ++ flask_website/_openid_auth.py | 83 ++++++++++++++++++++++++++++ flask_website/database.py | 87 ++++++++++++++++++++++++++++++ flask_website/views/mailinglist.py | 25 +++++---- websiteconfig.py | 11 ++++ 5 files changed, 196 insertions(+), 13 deletions(-) create mode 100644 flask_website/_openid_auth.py create mode 100644 flask_website/database.py create mode 100644 websiteconfig.py diff --git a/flask_website/__init__.py b/flask_website/__init__.py index 13fa2ef1..5ad87fc9 100644 --- a/flask_website/__init__.py +++ b/flask_website/__init__.py @@ -1,6 +1,9 @@ from flask import Flask, render_template +import websiteconfig as config + app = Flask(__name__) +app.debug = config.DEBUG @app.errorhandler(404) def not_found(error): diff --git a/flask_website/_openid_auth.py b/flask_website/_openid_auth.py new file mode 100644 index 00000000..d15c29b7 --- /dev/null +++ b/flask_website/_openid_auth.py @@ -0,0 +1,83 @@ +from __future__ import with_statement + +from time import time +from hashlib import sha1 +from contextlib import closing + +from openid.association import Association +from openid.store.interface import OpenIDStore +from openid.consumer.consumer import Consumer, SUCCESS, CANCEL +from openid.consumer import discover +from openid.store import nonce + +from sqlalchemy.orm import scoped_session +from sqlalchemy.exceptions import SQLError + +from flask import request, redirect, abort, url_for, flash +from flask_website.database import User, db_session + + +class WebsiteOpenIDStore(OpenIDStore): + """Implements the open store for the website using the database.""" + + def storeAssociation(self, server_url, association): + assoc = OpenIDAssociation( + server_url=server_url, + handle=association.handle, + secret=association.secret.encode('base64'), + issued=association.issued, + lifetime=association.lifetime, + assoc_type=association.assoc_type + ) + db_session.add(assoc) + + def getAssociation(self, server_url, handle=None): + q = OpenIDAssociation.query.filter_by(server_url=server_url) + if handle is not None: + q = q.filter_by(handle=handle) + result_assoc = None + for item in q.all(): + assoc = Association(item.handle, item.secret.decode('base64'), + item.issued, item.lifetime, item.assoc_type) + if assoc.getExpiresIn() <= 0: + self.removeAssociation(server_url, assoc.handle) + else: + result_assoc = assoc + return result_assoc + + def removeAssociation(self, server_url, handle): + return OpenIDAssociation.filter( + (OpenIDAssociation.server_url == server_url) & + (OpenIDAssociation.handle == handle) + ).delete() + + def useNonce(self, server_url, timestamp, salt): + if abs(timestamp - time()) > nonce.SKEW: + return False + rv = OpenIDUserNonces.query.filter( + (OpenIDUserNonces.server_url == server_url) & + (OpenIDUserNonces.timestamp == timestamp) & + (OpenIDUserNonces.salt == salt) + ).first() + if rv is not None: + return False + rv = OpenIDUserNonces(server_url=server_url, timestamp=timestamp, + salt=salt) + session.add(rv) + return True + + def cleanupNonces(self): + return OpenIDUserNonces.filter( + OpenIDUserNonces.timestamp <= int(time() - nonce.SKEW) + ).delete() + + def cleanupAssociations(self): + return OpenIDAssociation.filter( + OpenIDAssociation.lifetime < int(time()) + ).delete() + + def getAuthKey(self): + return sha1(config.SECRET_KEY).hexdigest()[:self.AUTH_KEY_LEN] + + def isDump(self): + return False diff --git a/flask_website/database.py b/flask_website/database.py new file mode 100644 index 00000000..3b962d5f --- /dev/null +++ b/flask_website/database.py @@ -0,0 +1,87 @@ +from datetime import datetime +from sqlalchemy import create_engine, MetaData, Table, Column, Integer, \ + String, DateTime, ForeignKey +from sqlalchemy.orm import scoped_session, sessionmaker, backref +from sqlalchemy.ext.declarative import declarative_base + +from flask_website import config + +engine = create_engine(config.DATABASE_URI) +db_session = scoped_session(sessionmaker(autocommit=False, + autoflush=False, + bind=engine)) + +def init_db(): + Model.metadata.create_all(bind=engine) + + +class Model(declarative_base()): + query = db_session.query_property() + + +class User(Model): + __tablename__ = 'users' + id = Column('user_id', Integer, primary_key=True) + openid = Column('openid', String(200)) + username = Column(String(40), unique=True) + password = Column(String(80)) + + def __init__(self, username, password): + self.username = username + self.password = password + + +class Category(Model): + __tablename__ = 'categories' + id = Column('category_id', Integer, primary_key=True) + name = Column(String(50)) + slug = Column(String(50)) + + +class Snippet(Model): + __tablename__ = 'snippets' + id = Column('snippet_id', Integer, primary_key=True) + author = ForeignKey(User, backref=backref('snippets', lazy='dynamic')) + category = ForeignKey(Category, backref=backref('snippets', lazy='dynamic')) + title = Column(String(200)) + body = Column(String) + pub_date = DateTime() + + def __init__(self, author, title, body): + self.author = author + self.title = title + self.body = body + self.pub_date = datetime.utcnow() + + +class Comment(Model): + __tablename__ = 'comments' + id = Column('comment_id', Integer, primary_key=True) + snippet = ForeignKey(Snippet, backref='lazy') + author = ForeignKey(User, backref=backref('comments', lazy='dynamic')) + title = Column(String(200)) + text = Column(String) + pub_date = DateTime() + + def __init__(self, author, title, text): + self.author = author + self.title = title + self.text = text + self.pub_date = datetime.utcnow() + + +class OpenIDAssociation(Model): + id = Column('association_id', Integer, primary_key=True) + server_url = Column(String(1024)) + handle = Column(String(255)) + secret = Column(String(255)) + issued = Column(Integer) + lifetime = Column(Integer) + assoc_type = Column(String(64)) + + +class OpenIDUserNonces(Model): + id = Column('user_nonce_id', Integer, primary_key=True) + server_url = Column(String(1024)) + timestamp = Column(Integer) + salt = Column(String(40)) diff --git a/flask_website/views/mailinglist.py b/flask_website/views/mailinglist.py index 47bedc8a..5f714ddc 100644 --- a/flask_website/views/mailinglist.py +++ b/flask_website/views/mailinglist.py @@ -4,10 +4,9 @@ from hashlib import md5 from werkzeug import parse_date from jinja2.utils import urlize from flask import Module, render_template, json, url_for, abort, Markup +from flask_website import config -mailinglist = Module(__name__) -THREADS_PER_PAGE = 15 -MAILINGLIST_PATH = os.path.join(os.path.dirname(__file__), '..', '..', '_mailinglist') +mailinglist = Module(__name__, url_prefix='/mailinglist') class Mail(object): @@ -54,14 +53,14 @@ class Thread(object): def get(year, month, day, slug): try: with open('%s/threads/%s-%02d-%02d/%s' % - (MAILINGLIST_PATH, year, month, day, slug)) as f: + (config.MAILINGLIST_PATH, year, month, day, slug)) as f: return Thread(json.load(f)) except IOError: pass @staticmethod def get_list(): - with open('%s/threads/threadlist' % MAILINGLIST_PATH) as f: + with open('%s/threads/threadlist' % config.MAILINGLIST_PATH) as f: return [Thread(x) for x in json.load(f)] @property @@ -71,25 +70,25 @@ class Thread(object): slug=self.slug) -@mailinglist.route('/mailinglist/') +@mailinglist.route('/') def index(): return render_template('mailinglist/index.html') -@mailinglist.route('/mailinglist/archive/', defaults={'page': 1}) -@mailinglist.route('/mailinglist/archive/page//') +@mailinglist.route('/archive/', defaults={'page': 1}) +@mailinglist.route('/archive/page//') def archive(page): all_threads = Thread.get_list() - offset = (page - 1) * THREADS_PER_PAGE - threads = all_threads[offset:offset + THREADS_PER_PAGE] + offset = (page - 1) * config.THREADS_PER_PAGE + threads = all_threads[offset:offset + config.THREADS_PER_PAGE] if page != 1 and not threads: abort(404) - return render_template('mailinglist/archive.html', - page_count=len(threads) // THREADS_PER_PAGE + 1, + return render_template('mailinglist/archive.html', page_count= + len(threads) // config.THREADS_PER_PAGE + 1, page=page, threads=threads) -@mailinglist.route('/mailinglist/archive/////') +@mailinglist.route('/archive/////') def show_thread(year, month, day, slug): thread = Thread.get(year, month, day, slug) if thread is None: diff --git a/websiteconfig.py b/websiteconfig.py new file mode 100644 index 00000000..7e7f7021 --- /dev/null +++ b/websiteconfig.py @@ -0,0 +1,11 @@ +import os + +_basedir = os.path.abspath(os.path.dirname(__file__)) + +DEBUG = False +SECRET_KEY = 'testkey' +DATABASE_URI = 'sqlite:///' + os.path.join(_basedir, 'flask-website.db') +MAILINGLIST_PATH = os.path.join(_basedir, '_mailinglist') +THREADS_PER_PAGE = 15 + +del os