Python Turbogears 2:身份验证、不同表中的密码、更新时的反馈

Python Turbogears 2:身份验证、不同表中的密码、更新时的反馈,python,pylons,turbogears,turbogears2,repoze.who,Python,Pylons,Turbogears,Turbogears2,Repoze.who,我使用turbogears 2.2来编写web应用程序,似乎它是一个非常强大的框架,但是有很多黑盒,比如身份验证,因为我不太了解它们(repoze.whoplugin) 要求 用户密码必须保存在不同的表中 减少数据库查询,不按每个请求加载用户;但是,请在需要时解决更新用户(例如权限)问题 不要通过每个用户查询加载密码 准备好使用openID和类似的登录 在身份验证期间拥有控制权(暂停用户等) 当前状态 我已经在model.auth-user、group、permission-和model.c

我使用turbogears 2.2来编写web应用程序,似乎它是一个非常强大的框架,但是有很多黑盒,比如身份验证,因为我不太了解它们(
repoze.who
plugin)

要求

  • 用户密码必须保存在不同的表中
  • 减少数据库查询,不按每个请求加载用户;但是,请在需要时解决更新用户(例如权限)问题
  • 不要通过每个用户查询加载密码
  • 准备好使用openID和类似的登录
  • 在身份验证期间拥有控制权(暂停用户等)
当前状态

我已经在model.auth-
user
group
permission
-和model.company中将基本模型定义为用户的外键。我将用户模型列为最重要的:

class User(DeclarativeBase):
    __tablename__ = 'user'

    id = Column(Integer, autoincrement = True, primary_key = True)
    email = Column(String, unique = True, nullable = False)
    name = Column(Unicode, nullable = False)
    surname = Column(Unicode, nullable = False)
    phone = Column(String)
    company_id = Column(Integer, ForeignKey('company.id', use_alter = True, name = 'fk_user_company_id'))
    company = relationship('Company', backref = 'users', foreign_keys = [company_id])
    _password = Column('password', Integer, ForeignKey('password.id'))
    active = Column(Boolean, default = True)

    _created = Column(DateTime, default = datetime.now)
    _updated = Column(DateTime)

    def __repr__(self):
        return ('<User: user_name=%s>' % (self.email))

    def __unicode__(self):
        return self.email

    @property
    def permissions(self):
        """Return a set with all permissions granted to the user."""
        perms = set()
        for g in self.groups:
            perms = perms | set(g.permissions)
        return perms

    @classmethod
    def by_email_address(cls, email):
        """Return the user object whose email address is ``email``."""
        return DBSession.query(cls).filter_by(email = email).first()

    @classmethod
    def by_username(cls, username):
        """Return the user object whose user name is ``username``."""
        return DBSession.query(cls).filter_by(_user_name = username).first()

    def _set_password(self, passw):
        ''' Set password. Password is saved in another table and columns references to it via ForeingKey'''
        passwd = DBSession.query(Password).filter_by(id = self._password).first()
        if passwd:
            passwd.password = passw
            DBSession.flush()
            self._password = passwd.id
        else:
            p = Password()
            p.password = passw
            DBSession.add(p)
            DBSession.flush()
            self._password = p.id

    def _get_password(self):
        ''' Return password via ForeingKey'''
        return DBSession.query(Password).filter_by(id = self._password).first().password

    password = synonym('_password', descriptor = property(_get_password, _set_password))

    def validate_password(self, password):
        ''' Validates password. This method has to be also in this class, because repoze.who requires it. '''
        hsh = sha256()
        if isinstance(password, unicode):
            password = password.encode('utf-8')
        hsh.update(password + str(self.password[:64]))
        return self.password[64:] == hsh.hexdigest()

    # This is a hack for repoze.who.plugins.sa, because there is written in code 'user_name' as keyword
    def _set_username(self, email):
        self.email = email

    def _get_username(self):
        return self.email

    def _get_created(self):
        return self._created.strftime(Settings.get('datetime', 'format'))

    def _set_created(self, dt):
        self._created = dt

    def _get_updated(self):
        return self._updated.strftime(Settings.get('datetime', 'format'))

    def _set_updated(self, dt):
        self._updated = dt

    created = synonym('_created', descriptor = property(_get_created, _set_created))
    updated = synonym('_updated', descriptor = property(_get_updated, _set_updated))

    user_name = synonym('email', descriptor = property(_get_username, _set_username))
    username = synonym('email', descriptor = property(_get_username, _set_username))

class Password (DeclarativeBase):
    __tablename__ = 'password'

    id = Column(Integer, autoincrement = True, primary_key = True)
    _password = Column('password', Unicode(128))

    @classmethod
    def _hash_password(cls, password):
        # Make sure password is a str because we cannot hash unicode objects
        if isinstance(password, unicode):
            password = password.encode('utf-8')
        salt = sha256()
        salt.update(os.urandom(60))
        hsh = sha256()
        hsh.update(password + salt.hexdigest())
        password = salt.hexdigest() + hsh.hexdigest()
        # Make sure the hashed password is a unicode object at the end of the
        # process because SQLAlchemy _wants_ unicode objects for Unicode cols
        if not isinstance(password, unicode):
            password = password.decode('utf-8')
        return password

    def _set_password(self, password):
        """Hash ``password`` on the fly and store its hashed version."""
        self._password = self._hash_password(password)

    def _get_password(self):
        """Return the hashed version of the password."""
        return self._password

    password = synonym('_password', descriptor = property(_get_password, _set_password))

    def validate_password(self, password):
        """
        Check the password against existing credentials.

        :param password: the password that was provided by the user to
            try and authenticate. This is the clear text version that we will
            need to match against the hashed one in the database.
        :type password: unicode object.
        :return: Whether the password is valid.
        :rtype: bool

        """
        hsh = sha256()
        if isinstance(password, unicode):
            password = password.encode('utf-8')
        hsh.update(password + str(self.password[:64]))
        return self.password[64:] == hsh.hexdigest()
以及
root.py
controller中的登录操作(我在某处得到的代码片段):

但是,他们通过每个请求获取用户信息以及用户密码:

SELECT "user".password AS user_password, "user".id AS user_id, "user".email AS user_email,
    "user".name AS user_name, "user".surname AS user_surname, "user".phone AS user_phone,
    "user".company_id AS user_company_id, "user".active AS user_active, "user"._created AS user__created,
    "user"._updated AS user__updated, company_1.ic AS company_1_ic,
    company_1.id AS company_1_id, company_1.name AS company_1_name, company_1.dic AS company_1_dic,
    company_1.address AS company_1_address, company_1.email AS company_1_email,
    company_1.is_supplier AS company_1_is_supplier, company_1.supplier_id AS company_1_supplier_id,
    company_1.active AS company_1_active, company_1.creator_id AS company_1_creator_id,
    company_1.updator_id AS company_1_updator_id, company_1._created AS company_1__created,
    company_1._updated AS company_1__updated 
FROM "user" LEFT OUTER JOIN company AS company_1 ON company_1.id = "user".company_id 
WHERE "user".email = %(email_1)s 
LIMIT %(param_1)s
最后一个问题

请告诉我如何理解Turbogears中的身份验证,并修复它以干净的方式满足所有要求?先谢谢你

更新


请提供TG 2.2的解决方案,因为无法升级。

我建议您升级到TurboGears 2.3,更新的版本支持
ApplicationAuthMetadata
中的
authenticate
方法,可以方便地提供用户名和密码有效性的自定义检查

标准
ApplicationAuthMetadata.authenticate
实现如下所示:

class ApplicationAuthMetadata(TGAuthMetadata):
    def __init__(self, sa_auth):
        self.sa_auth = sa_auth

    def authenticate(self, environ, identity):
        user = self.sa_auth.dbsession.query(self.sa_auth.user_class).filter_by(user_name=identity['login']).first()
        if user and user.validate_password(identity['password']):
            return identity['login']

    # Here are the get_user, get_groups and get_permissions

如果无法升级TurboGears,则必须实现一个更复杂的自定义repose.who验证器。您可以在

上找到一些关于它的文档,我建议您升级到TurboGears 2.3,较新的版本支持
ApplicationAuthMetadata
中的
authenticate
方法,可以方便地提供用户名和密码有效性的自定义检查

标准
ApplicationAuthMetadata.authenticate
实现如下所示:

class ApplicationAuthMetadata(TGAuthMetadata):
    def __init__(self, sa_auth):
        self.sa_auth = sa_auth

    def authenticate(self, environ, identity):
        user = self.sa_auth.dbsession.query(self.sa_auth.user_class).filter_by(user_name=identity['login']).first()
        if user and user.validate_password(identity['password']):
            return identity['login']

    # Here are the get_user, get_groups and get_permissions

如果无法升级TurboGears,则必须实现一个更复杂的自定义repose.who验证器。您可以在

上找到一些文档,谢谢您的回复,这意味着2.2版中没有身份验证方法,我找不到它?是否可以在2.2中使用_AuthenticationForgerPlugin类?我不确定是否要升级,变速箱不是问题,但我正在使用sqlalchemy迁移和sqlalchemy 0.8,因为我已经修复了其中的一个问题(使用postgres),一切正常。。。无论如何,我仍然不知道它到底是如何工作的,因为我在2.2和2.3中都缺少任何适当的文档。。。谁不告诉我怎么用TG。。。有什么想法吗?“齿轮箱”通过
sqla migrate
命令仍然可以使用sqlalchemy migrate,我真的建议您尝试升级,它可以让您更轻松地完成所需操作。如果您仍然坚持使用2.2,那么有一系列配置选项,可以在其中添加自定义验证器、标识符等。它们列在上。在您的案例中,您需要一个自定义验证器,您可以查看该验证器的回复。感谢您的回复,这意味着2.2版中没有身份验证方法,我找不到它?是否可以在2.2中使用_AuthenticationForgerPlugin类?我不确定是否要升级,变速箱不是问题,但我正在使用sqlalchemy迁移和sqlalchemy 0.8,因为我已经修复了其中的一个问题(使用postgres),一切正常。。。无论如何,我仍然不知道它到底是如何工作的,因为我在2.2和2.3中都缺少任何适当的文档。。。谁不告诉我怎么用TG。。。有什么想法吗?“齿轮箱”通过
sqla migrate
命令仍然可以使用sqlalchemy migrate,我真的建议您尝试升级,它可以让您更轻松地完成所需操作。如果您仍然坚持使用2.2,那么有一系列配置选项,可以在其中添加自定义验证器、标识符等。它们列在您的案例中,您需要一个自定义验证器,您可以查看repose.who文档
class ApplicationAuthMetadata(TGAuthMetadata):
    def __init__(self, sa_auth):
        self.sa_auth = sa_auth

    def authenticate(self, environ, identity):
        user = self.sa_auth.dbsession.query(self.sa_auth.user_class).filter_by(user_name=identity['login']).first()
        if user and user.validate_password(identity['password']):
            return identity['login']

    # Here are the get_user, get_groups and get_permissions