I've got something that seems to mostly work. Here's my caveats: * I had to make minor modifications to savisit.py and saprovider.py in order to make this work. If no one speaks up and tells me how I can avoid this, I'll submit these to trac.
* SqlAlchemyIdentityProvider() assumes that it needs to load User,
Group, and Permission tables in its __init__(). This means I either
have to forgo invoking the __init__() for the super class or fill the
config variables with fake class-table mappings. I've done the latter
for now but I'm not sure if there's a third way that's cleaner.
* I'm having difficulties with logout. When logging out and logging
back in I sometimes get a Flush error from SQLAlchemy:
File "/usr/lib/python2.4/site-packages/sqlalchemy/orm/mapper.py", line
840, in save_obj
raise exceptions.FlushError("New instance %s with identity key %s
conflicts with persistent instance %s" % (mapperutil.instance_str(obj),
str(instance_key), mapperutil.instance_str(existing)))
FlushError: New instance [EMAIL PROTECTED] with identity key
(<class 'fedora.accounts.tgfas.VisitIdentity'>,
('1a980e3f8e5f203d310290d7ffc176d6818ab905',), None) conflicts with
persistent instance [EMAIL PROTECTED]
I haven't traced this down yet as it's a transient error. I'm tempted
to think I'm encountering a race condition somewhere caused by logging
out and then back in too rapidly but I don't have any supporting
evidence yet.
Here's the files I'm using to achieve this:
* savisit.py.diff and saprovider.py.diff: Changes to these files so
subclassing them with the alternative database session works.
* tgfas.py: The visit model. The User and Group information will be
pulled from ldap.
* safasvisit.py: My visit manager
* sabzprovider.py: My teammate pulled together an identity provider that
authenticates against bugzilla until we code up something that targets
our account system. I further hacked it to not need a User table to
store information in. This isn't production quality -- just something
to work the kinks out of the identity-visit connection until we get our
account system hooked up.
-Toshio
--- /usr/lib/python2.4/site-packages/turbogears/visit/savisit.py 2006-11-13 15:09:48.000000000 -0800
+++ savisit.py 2007-01-12 16:34:14.000000000 -0800
@@ -29,7 +29,7 @@
def new_visit_with_key(self, visit_key):
visit = visit_class(visit_key=visit_key,
expiry=datetime.now()+self.timeout)
- session.save(visit)
+ visit_class.mapper.get_session().save(visit)
return Visit(visit_key, True)
def visit_for_key(self, visit_key):
@@ -50,13 +50,14 @@
def update_queued_visits(self, queue):
# TODO this should be made transactional
- table = class_mapper(visit_class).mapped_table
+ #table = class_mapper(visit_class).mapped_table
# Now update each of the visits with the most recent expiry
for visit_key,expiry in queue.items():
log.info("updating visit (%s) to expire at %s", visit_key,
expiry)
- get_engine().execute(table.update(table.c.visit_key==visit_key,
- values={'expiry': expiry}))
+ visit = visit_class.lookup_visit(visit_key)
+ visit.expiry = expiry
+ visit_class.mapper.get_session().flush()
class TG_Visit(ActiveMapper):
class mapping:
--- /usr/lib/python2.4/site-packages/turbogears/identity/saprovider.py 2006-09-06 13:48:39.000000000 -0700
+++ saprovider.py 2007-01-12 18:27:42.000000000 -0800
@@ -38,11 +38,11 @@
pass
# Attempt to load the user. After this code executes, there *WILL* be
# a _user attribute, even if the value is None.
- visit = session.query(visit_class).get_by(visit_key = self.visit_key)
+ visit = visit_class.get_by(visit_key = self.visit_key)
if not visit:
self._user = None
return None
- self._user = session.query(user_class).get(visit.user_id)
+ self._user = user_class.get(visit.user_id)
return self._user
user = property(_get_user)
@@ -89,15 +89,15 @@
if not self.visit_key:
return
try:
- visit = session.query(visit_class).get_by(visit_key=self.visit_key)
- session.delete(visit)
+ visit = visit_class.get_by(visit_key=self.visit_key)
+ visit_class.mapper.get_session().delete(visit)
# Clear the current identity
anon = SqlAlchemyIdentity(None,None)
identity.set_current_identity(anon)
except:
pass
else:
- session.flush()
+ visit_class.mapper.get_session().flush()
class SqlAlchemyIdentityProvider(object):
import turbogears
from datetime import *
from sqlalchemy import *
from sqlalchemy.ext.sessioncontext import SessionContext
from sqlalchemy.ext.assignmapper import assign_mapper
# The identity schema.
### FIXME:
# Steps to abstracting this:
# [x] Move this to its own model.py
# [x] in app.cfg, set the config to point to a different model.py
# [x] Move the visit/identity model.py into fedora-accounts.
### FIXME: Construct the default dburi from the /etc/sysconfig file like
# website.py does
#dburi = turbogears.config.get('fedora.fasdburi', 'sqlite://')
dburi = 'sqlite:////var/tmp/fasdb.sqlite'
engine = create_engine(dburi)
metadata = DynamicMetaData()
metadata.connect(dburi)
context = SessionContext(lambda:create_session(bind_to=engine))
visits_table = Table('visit', metadata,
Column('visit_key', String(40), primary_key=True),
Column('created', DateTime, nullable=False, default=datetime.now),
Column('expiry', DateTime)
)
visit_identity_table = Table('visit_identity', metadata,
Column('visit_key', String(40), primary_key=True),
Column('user_id', Integer, index=True)
)
'''
visit_identity_table = Table('visit_identity', metadata,
Column('visit_key', String(40), primary_key=True),
Column('user_id', Integer, ForeignKey('tg_user.user_id'), index=True)
)
groups_table = Table('tg_group', metadata,
Column('group_id', Integer, primary_key=True),
Column('group_name', Unicode(16), unique=True),
Column('display_name', Unicode(255)),
Column('created', DateTime, default=datetime.now)
)
users_table = Table('tg_user', metadata,
Column('user_id', Integer, primary_key=True),
Column('user_name', Unicode(16), unique=True),
Column('email_address', Unicode(255), unique=True),
Column('display_name', Unicode(255)),
Column('password', Unicode(40)),
Column('created', DateTime, default=datetime.now)
)
permissions_table = Table('permission', metadata,
Column('permission_id', Integer, primary_key=True),
Column('permission_name', Unicode(16), unique=True),
Column('description', Unicode(255))
)
user_group_table = Table('user_group', metadata,
Column('user_id', Integer, ForeignKey('tg_user.user_id')),
Column('group_id', Integer, ForeignKey('tg_group.group_id'))
)
group_permission_table = Table('group_permission', metadata,
Column('group_id', Integer, ForeignKey('tg_group.group_id')),
Column('permission_id', Integer, ForeignKey('permission.permission_id'))
)
'''
class Visit(object):
def lookup_visit(cls, visit_key):
return Visit.get(visit_key)
lookup_visit = classmethod(lookup_visit)
class VisitIdentity(object):
pass
'''
class Group(object):
"""
An ultra-simple group definition.
"""
pass
class User(object):
"""
Reasonably basic User definition. Probably would want additional
attributes.
"""
def permissions(self):
perms = set()
for g in self.groups:
perms = perms | set(g.permissions)
return perms
permissions = property(permissions)
class Permission(object):
pass
'''
assign_mapper(context, Visit, visits_table)
assign_mapper(context, VisitIdentity, visit_identity_table)
'''
assign_mapper(context, VisitIdentity, visit_identity_table,
properties=dict(users=relation(User, backref='visit_identity')))
assign_mapper(context, User, users_table)
assign_mapper(context, Group, groups_table,
properties=dict(users=relation(User,secondary=user_group_table, backref='groups')))
assign_mapper(context, Permission, permissions_table,
properties=dict(groups=relation(Group,secondary=group_permission_table, backref='permissions')))
'''
from turbogears.visit.savisit import SqlAlchemyVisitManager
from turbogears import config
from turbogears.util import load_class
from turbogears.database import bind_meta_data
class SaFASVisitManager(SqlAlchemyVisitManager):
'''Visit Manager that talks to the Fedora Account System'''
def __init__(self, timeout):
global visit_class
super(SaFASVisitManager, self).__init__(timeout)
visit_class_path = config.get('visit.safasprovider.model',
'fedora.accounts.tgfas.Visit')
visit_class = load_class(visit_class_path)
bind_meta_data()
# $Id: sobzprovider.py,v 1.2 2007/01/03 21:21:18 lmacken Exp $
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""
This plugin provides authentication of passwords against Bugzilla via XML-RPC
"""
import logging
import xmlrpclib
from turbogears.identity.saprovider import *
from turbogears import config
from fedora.accounts.tgfas import VisitIdentity
log = logging.getLogger(__name__)
class FakeUser(object):
def __init__(self, user_id, user_name, user, groups, permissions):
self.user_id = user_id
self.user_name = user_name
self.user = user
self.groups = groups
self.permissions = permissions
self.display_name = 'Toshio Kuratomi'
class FakeGroup(object):
def __init__(self, gid):
self.group_id = gid
self.group_name = 'first' + str(gid)
self.display_name = 'Group: #' + str(gid)
class SaBugzillaIdentity(SqlAlchemyIdentity):
def _get_user(self):
visit = visit_class.get_by(visit_key = self.visit_key)
if not visit:
self._user = None
return None
self._user = FakeUser(1, 'my fake user name', None, (FakeGroup(1), FakeGroup(2), FakeGroup(3)), None)
return self._user
user = property(_get_user)
class SaBugzillaIdentityProvider(SqlAlchemyIdentityProvider):
"""
IdentityProvider that authenticates users against Bugzilla via XML-RPC
"""
def __init__(self):
super(SaBugzillaIdentityProvider, self).__init__()
self.bz_server = config.get('identity.sabugzilla.bz_server')
global visit_class
visit_class_path = config.get("identity.saprovider.model.visit", None)
log.info("Loading: %s", visit_class_path)
visit_class = load_class(visit_class_path)
def validate_identity(self, user_name, password, visit_key):
user = FakeUser(1, user_name, None, (FakeGroup(1), FakeGroup(2), FakeGroup(3)), None)
if not self.validate_password(user, user_name, password):
log.warning("Invalid password for %s" % user_name)
return None
log.info("Login successful for %s" % user_name)
link = visit_class.get_by(visit_key=visit_key)
if not link:
link = visit_class(visit_key=visit_key, user_id = user.user_id)
visit_class.mapper.get_session().save(link)
else:
link.user_id = user.user_id
visit_class.mapper.get_session().flush()
return SaBugzillaIdentity(visit_key, user)
def validate_password(self, user, user_name, password):
"""
Complete hack, but it works.
Request bug #1 with the given username and password. If a Fault is
thrown, the username/pass is invalid; else, we're good to go.
"""
try:
server = xmlrpclib.Server(self.bz_server)
server.bugzilla.getBugSimple('1', user_name, password)
except xmlrpclib.Fault:
return False
return True
def load_identity(self, visit_key):
return SaBugzillaIdentity(visit_key)
signature.asc
Description: This is a digitally signed message part

