Reading the sqlalchemy docs says that using DBSession.merge is the ticket. That does solve the problem, but I don't understand why that user would be in a different session. In fact, I'm thinking specifically it SHOULD NOT be.
This code is run in MediaCoreAuthenticatorPlugin.authenticate. I've modded the code as below and it works now. My environment authenticates to ldap for staff and imap for students, which I know from parsing their username:
from pylons import config as pylonsconfig
def authenticate(self, environ, identity, notagain=False):
login = super(MediaCoreAuthenticatorPlugin, self).authenticate(environ, identity)
if login is None:
if notagain:
return None # prevent infinite loop
username = identity['login']
password = identity['password']
if re.match(r'^[a-z]+[0-9]{2}$', username):
auth_to_use = pylonsconfig['imap']
else:
auth_to_use = pylonsconfig['ldap']
if not auth_to_use.auth(username, password):
return None
else:
# Use the model to create the user which automagically gets put in the database
user = User()
user.display_name = username
user.user_name = username
user.email_address = "{}@{}".format(user.user_name, auth_to_use.default_domain())
user.password = u'uselesspassword#%^^#@'
user.groups = auth_to_use.default_groups()
try:
#actually add the user
DBSession.add(user)
DBSession.commit()
except IntegrityError, e:
DBSession.rollback()
return None
except InvalidRequestError, e:
new_user = DBSession.merge(user)
DBSession.add(new_user) # TODO: Another try block
DBSession.commit()
except Exception, e:
DBSession.rollback()
return None
# Now repoze.who should be able to login
return self.authenticate(environ, identity, notagain=True)
user = self.get_user(login)
# The return value of this method is used to identify the user later on.
# As the username can be changed, that's not really secure and may
# lead to confusion (user is logged out unexpectedly, best case) or
# account take-over (impersonation, worst case).
# The user ID is considered constant and likely the best choice here.
return user.user_id