[server:main]
use = egg:Paste#http
-host = 127.0.0.1
+host = 0.0.0.0
port = 5000
[app:main]
# WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*
# Debug mode will enable the interactive debugging tool, allowing ANYONE to
# execute malicious code after an exception is raised.
-#set debug = false
+set debug = false
# Logging configuration
always_scan=config['debug'])
map.minimization = False
+ require_POST = dict(conditions={'method': ['POST']})
+
# The ErrorController route (handles 404/500 error pages); it should
# likely stay at the top, ensuring it can always be resolved
map.connect('/error/{action}', controller='error')
map.connect('/error/{action}/{id}', controller='error')
- # CUSTOM ROUTES HERE
+ map.connect('/account/login', controller='account', action='login')
+ map.connect('/account/login_begin', controller='account', action='login_begin', **require_POST)
+ map.connect('/account/login_finish', controller='account', action='login_finish')
map.connect('/{controller}/{action}')
map.connect('/{controller}/{action}/{id}')
--- /dev/null
+import logging
+from openid.consumer.consumer import Consumer
+from openid.extensions.sreg import SRegRequest, SRegResponse
+from openid.store.filestore import FileOpenIDStore
+from sqlalchemy.orm.exc import NoResultFound
+
+from pylons import request, response, session, tmpl_context as c
+from pylons.controllers.util import abort, redirect_to
+from routes import url_for, request_config
+
+from floof import model
+from floof.model.meta import Session
+from floof.lib.base import BaseController, render
+
+log = logging.getLogger(__name__)
+
+class AccountController(BaseController):
+
+ openid_store = FileOpenIDStore('/var/tmp')
+
+ def login(self):
+ return render('/login.mako')
+
+ def login_begin(self):
+ """Step one of logging in with OpenID; we redirect to the provider"""
+
+ cons = Consumer(session=session, store=self.openid_store)
+ auth_request = cons.begin(request.params['identity_url'])
+ sreg_req = SRegRequest(optional=['nickname', 'email', 'dob', 'gender',
+ 'country', 'language', 'timezone'])
+ auth_request.addExtension(sreg_req)
+
+ host = request.headers['host']
+ protocol = request_config().protocol
+ return_url = url_for(host=host, controller='account', action='login_finish')
+ new_url = auth_request.redirectURL(return_to=return_url,
+ realm=protocol + '://' + host)
+ redirect_to(new_url)
+
+ def login_finish(self):
+ """Step two of logging in; the OpenID provider redirects back here."""
+
+ cons = Consumer(session=session, store=self.openid_store)
+ host = request.headers['host']
+ return_url = url_for(host=host, controller='account', action='login_finish')
+ res = cons.complete(request.params, return_url)
+
+ if res.status != 'success':
+ return 'Error! %s' % res.message
+
+ try:
+ # Grab an existing user record, if one exists
+ q = Session.query(model.User) \
+ .filter(model.User.identity_urls.any(url=res.identity_url))
+ user = q.one()
+ except NoResultFound:
+ # Try to pull a name out of the SReg response
+ sreg_res = SRegResponse.fromSuccessResponse(res)
+ try:
+ username = sreg_res['nickname']
+ except:
+ username = 'Anonymous'
+
+ # Create db records
+ user = model.User(name=username)
+ Session.add(user)
+ identity_url = model.IdentityURL(url=res.identity_url)
+ user.identity_urls.append(identity_url)
+ Session.commit()
+
+ # Remember who's logged in, and we're good to go
+ session['user_id'] = user.id
+ session.save()
+
+ return "Hello, %s from %s" % (user.name, res.identity_url)
Provides the BaseController class for subclassing.
"""
+from pylons import session, tmpl_context as c
from pylons.controllers import WSGIController
from pylons.templating import render_mako as render
+from floof import model
from floof.model import meta
class BaseController(WSGIController):
+ def __before__(self, action, **params):
+ # Fetch current user object
+ try:
+ c.user = meta.Session.query(model.User).get(session['user_id'])
+ except:
+ pass
+
def __call__(self, environ, start_response):
"""Invoke the Controller"""
# WSGIController.__call__ dispatches to the Controller method
from floof.model import meta
+# Tables are defined in separate files and imported to here
+from floof.model.users import *
+
def init_model(engine):
"""Call me before using any of the tables or classes in the model"""
## Reflected tables must be defined and mapped here
meta.Session.configure(bind=engine)
meta.engine = engine
-
-## Non-reflected tables may be defined and mapped at module level
-#foo_table = sa.Table("Foo", meta.metadata,
-# sa.Column("id", sa.types.Integer, primary_key=True),
-# sa.Column("bar", sa.types.String(255), nullable=False),
-# )
-#
-#class Foo(object):
-# pass
-#
-#orm.mapper(Foo, foo_table)
-
-
-## Classes for reflected tables may be defined here, but the table and
-## mapping itself must be done in the init_model function
-#reflected_table = None
-#
-#class Reflected(object):
-# pass
"""SQLAlchemy Metadata and Session object"""
from sqlalchemy import MetaData
+from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
-__all__ = ['Session', 'engine', 'metadata']
+__all__ = ['Session', 'engine', 'TableBase']
# SQLAlchemy database engine. Updated by model.init_model()
engine = None
# SQLAlchemy session manager. Updated by model.init_model()
Session = scoped_session(sessionmaker())
-# Global metadata. If you have multiple databases with overlapping table
-# names, you'll need a metadata for each database
-metadata = MetaData()
+# Base class for declarative; creates its own metadata object
+TableBase = declarative_base()
+metadata = TableBase.metadata
--- /dev/null
+from sqlalchemy import Column, ForeignKey
+from sqlalchemy.orm import relation
+from sqlalchemy.types import Integer, Unicode
+
+from floof.model import meta
+
+__all__ = ['User', 'IdentityURL']
+
+class User(meta.TableBase):
+ __tablename__ = 'users'
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode(length=20), nullable=False)
+
+class IdentityURL(meta.TableBase):
+ __tablename__ = 'identity_urls'
+ url = Column(Unicode(length=255), primary_key=True)
+ user_id = Column(Integer, ForeignKey('users.id'))
+
+IdentityURL.user = relation(User, lazy=False, backref="identity_urls")
--- /dev/null
+<%def name="title()">Untitled</%def>\
+<!DOCTYPE html>
+<html>
+<head>
+<title>${title()} — Floof</title>
+</head>
+<body>
+% if c.user:
+<p>sup ${c.user.name}</p>
+% else:
+<p>not logged in</p>
+% endif
+${next.body()}
+</body>
+</html>
--- /dev/null
+<%inherit file="base.mako" />
--- /dev/null
+<%inherit file="base.mako" />
+
+<form action="${url(controller='account', action='login_begin')}" method="POST">
+<p>
+ Identity URL: <input type="text" name="identity_url">
+ <input type="submit" value="Log in">
+</p>
+</form>
--- /dev/null
+from floof.tests import *
+
+class TestAccountController(TestController):
+
+ def test_index(self):
+ response = self.app.get(url(controller='account', action='index'))
+ # Test response...
install_requires=[
"Pylons>=0.9.7",
"SQLAlchemy>=0.5",
+ 'openid',
],
setup_requires=["PasteScript>=1.6.3"],
packages=find_packages(exclude=['ez_setup']),