6.1. 会议

会话是链接到经过身份验证的用户的对象。这个 Session.new_cnx 方法返回链接到该会话的新连接。

6.2. 连接

连接提供 .execute 用于查询数据源的方法,以及 .commit.rollback 交易管理方法。

6.2.1. 连接类型

有两种连接。

  • normal connections 是最常见的:它们与用户相关,并带有随用户凭据一起提供的安全检查

  • internal connections 拥有所有的能力;它们也只在少数情况下使用,在这些情况下,您手头没有足够的会话,例如:用户身份验证、多源环境中的数据同步

正常连接通常命名为 _cw 在大多数应用程序对象中,有时只是 session .

内部连接可从 Repository 对象和的用法如下:

with self.repo.internal_cnx() as cnx:
    do_stuff_with(cnx)
    cnx.commit()

连接应始终用作上下文管理器,以避免泄漏。

6.2.1.1. python/rql应用程序接口

开发用于与rql接口的python api是从标准的db api中获得灵感的,但是由于 execute 直接返回结果,没有 cursor 概念。

execute(rqlstring, args=None, build_descr=True)
RQString公司

要执行的RQL查询(Unicode)

阿尔茨海默病

如果查询包含替换,则包含要使用的值的字典

这个 Connection 对象拥有方法 commitrollback .你 不需要使用它们 在开发基于 CubicWeb 框架,因为它决定事务的结束取决于查询执行的成功。但是,它们在测试或自定义控制器等其他上下文中很有用。

注解

如果查询生成与安全性相关的错误 (Unauthorized )或者为了正直 (ValidationError )事务仍可以继续,但您将无法提交,需要回滚才能启动新事务。

此外,如果在提交期间发生错误,则会自动执行回滚。

注解

A ValidationError 有一个 entity 属性。在CubicWeb中,此attribute设置为实体的EID(不是对实体本身的引用)。

6.2.1.2. 从视图或钩子执行RQL查询

当您在Web界面的代码中时,连接由请求对象处理。您不应该直接访问它,而是使用 execute 可根据要求直接使用的方法,例如:

rset = self._cw.execute(rqlstring, kwargs)

类似地,在服务器端(如钩子中),没有请求对象(因为您直接在数据服务器中),所以您必须使用连接对象的Execute方法。

6.2.1.3. 正确使用 .execute

假设您想要得到配置C中的T,这就意味着:

self._cw.execute('Any T WHERE T in_conf C, C eid %s' % entity.eid)

但它必须以一种语法编写,这种语法将受益于在RQL服务器端使用缓存:

self._cw.execute('Any T WHERE T in_conf C, C eid %(x)s', {'x': entity.eid})

语法树为“generic”rql构建一次,可以与许多不同的eid一起使用。rql in运算符是此规则的一个例外。

self._cw.execute('Any T WHERE T in_conf C, C name IN (%s)'
                 % ','.join(['foo', 'bar']))

或者,可以从 entity.related() 方法(当您在实体上使用属性访问表示法来获取关系时,ORM在hood下使用该方法)。最初的请求将被转换为:

entity.related('in_conf', 'object')

此外,这还得益于“获取属性”策略(请参见 加载的属性和默认排序管理 )可以选择在类元素上定义,该元素表示当通过ORM加载实体时,还必须加载哪些属性。

6.2.1.4. 这个 ResultSet API

结果集实例是一个非常常见的操作对象。它们有一个丰富的API,如下所示,但我们想重点介绍一系列在日常实践中非常有用的方法:

  • __str__() (应用者 print )对底层RQL表达式和内部数据进行了非常有用的概述;出于调试目的,这是不可避免的。

  • printable_rql() 以字符串形式返回格式良好的RQL表达式;构建视图非常有用

  • entities() 返回结果集所有实体上的生成器

  • get_entity(row, col) 获取行、列坐标处的实体;最常用的结果集方法之一

6.2.2. 会话的身份验证和管理

认证过程是一个芭蕾舞,包括几个舞者:

  • 通过它 get_session 方法顶级应用程序对象 CubicWebPublisher )每当Web请求出现时将打开会话;它要求 session manager 要打开会话(将Web请求对象作为上下文),请使用 open_session

    • 会话管理器询问其身份验证管理器(它是 component )验证请求(使用 authenticate

      • 身份验证管理器依次向其身份验证信息检索器请求一个登录名和一个包含其他凭据元素的不透明对象(调用 authentication_information ,每次都给出请求对象

        • 默认检索器(命名为 LoginPasswordRetriever )将反过来推迟登录和获取请求对象的密码(这取决于身份验证模式 (cookiehttp )将执行相应的操作并返回登录名和密码)

      • 身份验证管理器成功后,询问 Repository 要与找到的凭据连接的对象(使用 connect

        • 存储库对象要求对其所有支持 CWUser 具有给定凭据的实体;成功时,它可以构建cwuser实体,从中 Session 对象;它返回会话ID

          • 然后,源将工作委托给一个authentifier类,该类定义了 authenticate 方法(例如,本机源将根据提供的凭据查询数据库)

      • 身份验证管理器成功后将回调 _all_ 带的检索器 authenticated 并返回其身份验证数据(失败时,它将尝试匿名登录,或者,如果配置禁止,则引发 AuthenticationError

6.2.3. 正在写入身份验证插件

有时,CubicWeb现成的身份验证方案(cookie和http)是不够的。目前,这种方案已经很多了,框架不能提供全部方案,但正如上面的顺序所示,它是可扩展的。

编写身份验证插件时必须考虑两个级别:Web客户端和存储库。

我们发明了一个场景,在每一端都有一个新的插件是有意义的:一些中间件将进行预身份验证,在适当的情况下添加一个新的HTTP x-foo-user header to the query before it reaches the CubicWeb instance. For a concrete example of this, see the trustedauth 立方体。

6.2.3.1. 存储库身份验证插件

在存储库方面,可以使用以下代码注册源身份验证程序:

from cubicweb.server.sources import native

class FooAuthentifier(native.LoginPasswordAuthentifier):
    """ a source authentifier plugin
    if 'foo' in authentication information, no need to check
    password
    """
    auth_rql = 'Any X WHERE X is CWUser, X login %(login)s'

    def authenticate(self, session, login, **kwargs):
        """return CWUser eid for the given login
        if this account is defined in this source,
        else raise `AuthenticationError`
        """
        session.debug('authentication by %s', self.__class__.__name__)
        if 'foo' not in kwargs:
            return super(FooAuthentifier, self).authenticate(session, login, **kwargs)
        try:
            rset = session.execute(self.auth_rql, {'login': login})
            return rset[0][0]
        except Exception, exc:
            session.debug('authentication failure (%s)', exc)
        raise AuthenticationError('foo user is unknown to us')

由于存储库身份验证程序不是AppObjects,因此我们必须通过 server_startup 钩子。

class ServerStartupHook(hook.Hook):
    """ register the foo authenticator """
    __regid__ = 'fooauthenticatorregisterer'
    events = ('server_startup',)

    def __call__(self):
        self.debug('registering foo authentifier')
        self.repo.system_source.add_authentifier(FooAuthentifier())

6.2.3.2. Web身份验证插件

class XFooUserRetriever(authentication.LoginPasswordRetriever):
    """ authenticate by the x-foo-user http header
    or just do normal login/password authentication
    """
    __regid__ = 'x-foo-user'
    order = 0

    def authentication_information(self, req):
        """retrieve authentication information from the given request, raise
        NoAuthInfo if expected information is not found
        """
        self.debug('web authenticator building auth info')
        try:
           login = req.get_header('x-foo-user')
           if login:
               return login, {'foo': True}
           else:
               return super(XFooUserRetriever, self).authentication_information(self, req)
        except Exception, exc:
           self.debug('web authenticator failed (%s)', exc)
        raise authentication.NoAuthInfo()

    def authenticated(self, retriever, req, cnx, login, authinfo):
        """callback when return authentication information have opened a
        repository connection successfully. Take care req has no session
        attached yet, hence req.execute isn't available.

        Here we set a flag on the request to indicate that the user is
        foo-authenticated. Can be used by a selector
        """
        self.debug('web authenticator running post authentication callback')
        cnx.foo_user = authinfo.get('foo')

authenticated 方法向连接对象添加一个属性(以一种令人信服的稍微有点黑客的方式)。反过来,这可以用来构建一个选择器调度,基于用户是否经过了预身份验证的事实。

@objectify_selector
def foo_authenticated(cls, req, rset=None, **kwargs):
    if hasattr(req.cnx, 'foo_user') and req.foo_user:
        return 1
    return 0

6.2.3.3. 完整会话和连接API