金字塔2.0的新增功能

本文解释了 Pyramid 版本2.0与前一版本相比, Pyramid 1.10条。它还记录了两个版本之间的向后不兼容以及添加到 Pyramid 2.0,以及软件依赖关系的更改和显著的文档添加。

备注

这是的第一个版本 Pyramid 这不支持Python2,Python2现已停产,不再接收PSF的关键安全更新。

功能添加

Pyramid 2.0中的新增功能如下:

  • 金字塔1.x的身份验证和授权策略已合并到单个 security policy 在金字塔2.0中。有关如何迁移到新安全策略的详细信息,请参阅 升级身份验证/授权 。身份验证和授权策略仍然可以使用,并且暂时将继续正常运行。

    添加了新的安全API,以支持身份验证和授权系统的全面改革。朗读 升级身份验证/授权 获取有关使用此新系统的信息。

    请参阅https://github.com/Pylons/pyramid/pull/3465

  • 暴露的 pyramid.authorization.ALL_PERMISSIONSpyramid.authorization.DENY_ALL 这样,所有与ACL相关的常量现在都可以从 pyramid.authorization 命名空间。请参阅https://github.com/Pylons/pyramid/pull/3563

  • 更改了默认值 serializer 在……上面 pyramid.session.SignedCookieSessionFactory 要使用 pyramid.session.JSONSerializer 而不是 pyramid.session.PickleSerializer 。朗读 升级会话序列化 有关进行此更改的原因的详细信息,请参阅。请参阅https://github.com/Pylons/pyramid/pull/3413

  • 现在可以使用以下命令控制路由模式在与路由前缀组成时是否包含尾随劈开 config.include(..., route_prefix=...)with config.route_prefix_context(...) 。这可以通过指定空模式并设置新参数来实现 inherit_slash=True 。例如:

    with config.route_prefix_context('/users'):
        config.add_route('users', '', inherit_slash=True)
    

    在本例中,生成的模式将为 /users 。同样,如果路由前缀是 /users/ 那么最终的图案将是 /users/ 。如果 pattern 曾经是 '/' ,那么最终的图案将始终是 /users/ 。此新设置仅在以下情况下可用:提供给的图案 add_route 是空字符串 ('' )。请参阅https://github.com/Pylons/pyramid/pull/3420

  • 一个新参数, allow_no_origin ,已添加到 pyramid.config.Configurator.set_default_csrf_options() 以及 pyramid.csrf.check_csrf_origin() 。此选项控制是否拒绝请求(如果没有 OriginReferer 标头-通常是用户将其浏览器配置为不发送 Referer 出于隐私原因,即使是在相同的域请求上也会显示标头。默认情况下,拒绝来源未知的请求。也可以允许特殊的 Origin: null 标头,将其添加到 pyramid.csrf_trusted_origins 在设置中列出。请参阅https://github.com/Pylons/pyramid/pull/3512和https://github.com/Pylons/pyramid/pull/3518

  • 一个新参数, check_origin ,已添加到 pyramid.config.Configurator.set_default_csrf_options() 这将完全禁用来源检查。请参阅https://github.com/Pylons/pyramid/pull/3518

  • 已添加 pyramid.interfaces.IPredicateInfo 它将传递给谓词工厂的对象定义为它们的第二个参数。请参阅https://github.com/Pylons/pyramid/pull/3514

  • 添加了对服务压缩前的静电资产的支持,方法是使用 content_encodings 论证 pyramid.config.Configurator.add_static_view()pyramid.static.static_view() 。请参阅https://github.com/Pylons/pyramid/pull/3537

  • 修复 DeprecationWarning 通过使用 imp 模块。请参阅https://github.com/Pylons/pyramid/pull/3553

  • 通过以下方式创建的特性 config.add_request_method(..., property=True) or request.set_property used to be readonly. They can now be overridden via request.foo = ... 在删除该值之前,它将返回被覆盖的值。这在模拟测试中的请求属性时最有用。请参阅https://github.com/Pylons/pyramid/pull/3559

  • 完成的回调现在作为 closer 作为以下内容的一部分调用的 pyramid.scripting.prepare()pyramid.paster.bootstrap() 。请参阅https://github.com/Pylons/pyramid/pull/3561

  • 已添加 pyramid.request.RequestLocalCache 它可用于创建跨请求共享的简单对象,并可用于存储每个请求的数据。当数据源在请求本身的外部时,这很有用。通常,实例化属性通过以下方式在请求上使用 pyramid.config.Configurator.add_request_method() ,或 pyramid.decorator.reify 。当数据在访问请求属性时按需生成时,这些功能非常有用。但是,通常情况是,数据是在访问其他系统时生成的,然后我们希望在请求期间缓存数据。请参阅https://github.com/Pylons/pyramid/pull/3561

  • 不再定义 pyramid.request.Request.json_body 它已经由WebOb提供。这使得现在可以设置该属性。请参阅https://github.com/Pylons/pyramid/pull/3447

  • 从以下位置改进调试信息 pyramid.view.view_config 装饰师。请参阅https://github.com/Pylons/pyramid/pull/3483

  • pserve 现在将详细消息输出到 stderr 而不是 stdout 要规避默认情况下存在的缓冲问题,请执行以下操作 stdout 。请参阅https://github.com/Pylons/pyramid/pull/3593

废弃

升级身份验证/授权

备注

需要注意的是,主体和ACL在 Pyramid 不会消失,不会被弃用,也不会被移除。大多数ACL功能在其当前位置都已弃用,并已移动到 pyramid.authorization 模块。主要的变化是,它们现在比以前更可选,并进行了修改,以使顶级API不那么固执己见,也更简单。

Pyramid 提供一组简单的API,用于在应用程序中插入允许/拒绝的语义。

金字塔1.x的身份验证和授权策略已合并到单个 security policy 在金字塔2.0中。身份验证和授权策略仍然可以使用,并将继续正常运行,但它们已被弃用,可能会在即将发布的版本中删除支持。

新的安全策略应该实施 pyramid.interfaces.ISecurityPolicy 可以通过 security_policy 的参数 pyramid.config.Configuratorpyramid.config.Configurator.set_security_policy() .

该保单包含 pyramid.interfaces.ISecurityPolicy.authenticated_userid()pyramid.interfaces.ISecurityPolicy.remember() ,具有与传统身份验证策略中相同的方法签名。它还包含 pyramid.interfaces.ISecurityPolicy.forget() ,但现在接受方法签名中的关键字参数。

新的安全策略添加了 identity ,它是表示与当前请求相关联的用户的对象。可以通过以下方式访问该身份 pyramid.request.Request.identity 。该对象可以是任何形状,例如简单的ID字符串或ORM对象。

的概念 principals 已从请求对象、安全策略和视图/路由谓词中删除。主体被替换为 identity 。这个 pyramid.interfaces.ISecurityPolicy.permits() 方法被提供给 requestcontext ,以及 permissions ,并且现在可以使用 identity 对象,或派生主体,以它认为对应用程序必要的任何方式,而不限于由字符串表示的主体列表。此更改为授权实现提供了更大的灵活性,尤其是那些不匹配ACL模式的授权实现。如果您以前使用的是 pyramid.authorization.ACLAuthorizationPolicy ,您可以通过编写自己的代码来获得相同的结果 permits 方法使用 pyramid.authorization.ACLHelper 。有关实施ACL的更多详细信息,请参阅 实现ACL授权

金字塔不提供任何内置安全策略。现在,帮助器提供了身份验证和授权策略的类似功能,可用于实现您自己的安全策略。传统身份验证策略的功能大致对应于以下助手:

身份验证策略

安全策略帮助程序

pyramid.authentication.SessionAuthenticationPolicy

pyramid.authentication.SessionAuthenticationHelper

pyramid.authentication.AuthTktAuthenticationPolicy

pyramid.authentication.AuthTktCookieHelper

pyramid.authentication.BasicAuthAuthenticationPolicy

使用 pyramid.authentication.extract_http_basic_credentials() 检索凭据。

pyramid.authentication.RemoteUserAuthenticationPolicy

REMOTE_USER 可以通过 request.environ.get('REMOTE_USER') .

pyramid.authentication.RepozeWho1AuthenticationPolicy

没有等价物。

从内置策略升级

让我们假设您的应用程序正在使用内置的身份验证和授权策略,如 pyramid.authentication.AuthTktAuthenticationPolicy 。例如:

 1def groupfinder(userid, request):
 2    # do some db lookups to verify userid, then return
 3    # None if not recognized, or a list of principals
 4    if userid == 'editor':
 5        return ['group:editor']
 6
 7authn_policy = AuthTktAuthenticationPolicy('seekrit', callback=groupfinder)
 8authz_policy = ACLAuthorizationPolicy()
 9config.set_authentication_policy(authn_policy)
10config.set_authorization_policy(authz_policy)

我们可以很容易地写出我们自己的 pyramid.interfaces.ISecurityPolicy 实施:

 1from pyramid.authentication import AuthTktCookieHelper
 2from pyramid.authorization import ACLHelper, Authenticated, Everyone
 3
 4class MySecurityPolicy:
 5    def __init__(self, secret):
 6        self.helper = AuthTktCookieHelper(secret)
 7
 8    def identity(self, request):
 9        # define our simple identity as None or a dict with userid and principals keys
10        identity = self.helper.identify(request)
11        if identity is None:
12            return None
13        userid = identity['userid']  # identical to the deprecated request.unauthenticated_userid
14
15        # verify the userid, just like we did before with groupfinder
16        principals = groupfinder(userid, request)
17
18        # assuming the userid is valid, return a map with userid and principals
19        if principals is not None:
20            return {
21                'userid': userid,
22                'principals': principals,
23            }
24
25    def authenticated_userid(self, request):
26        # defer to the identity logic to determine if the user id logged in
27        # and return None if they are not
28        identity = request.identity
29        if identity is not None:
30            return identity['userid']
31
32    def permits(self, request, context, permission):
33        # use the identity to build a list of principals, and pass them
34        # to the ACLHelper to determine allowed/denied
35        identity = request.identity
36        principals = set([Everyone])
37        if identity is not None:
38            principals.add(Authenticated)
39            principals.add(identity['userid'])
40            principals.update(identity['principals'])
41        return ACLHelper().permits(context, principals, permission)
42
43    def remember(self, request, userid, **kw):
44        return self.helper.remember(request, userid, **kw)
45
46    def forget(self, request, **kw):
47        return self.helper.forget(request, **kw)
48
49config.set_security_policy(MySecurityPolicy('seekrit'))

这比以前稍微冗长了一些,但是它很容易编写,而且对于更高级的应用程序来说,它的可扩展性要高得多。

有关实现安全策略的更多文档,请参阅 编写安全策略 .

从第三方策略升级

仿制药 security policy 可以编写为使用旧的身份验证和授权策略。请注意,在采用此方法时,某些新功能(如标识)可能不具有可扩展性和良好的易用性,但可以这样做以简化过渡:

 1class ShimSecurityPolicy:
 2    def __init__(self, authn_policy, authz_policy):
 3        self.authn_policy = authn_policy
 4        self.authz_policy = authz_policy
 5
 6    def authenticated_userid(self, request):
 7        return self.authn_policy.authenticated_userid(request)
 8
 9    def permits(self, request, context, permission):
10        principals = self.authn_policy.effective_principals(request)
11        return self.authz_policy.permits(context, principals, permission)
12
13    def remember(self, request, userid, **kw):
14        return self.authn_policy.remember(request, userid, **kw)
15
16    def forget(self, request, **kw):
17        return self.authz_policy.forget(request, **kw)

与旧式身份验证/授权策略和API兼容

如果您正在从使用传统身份验证和授权策略和API的应用程序升级,则情况将继续正常运行。新系统是向后兼容的,API仍然存在。我们非常鼓励您进行升级,以便接受新功能。旧版API已弃用,将来可能会删除。

新的 pyramid.request.Request.identity 属性将输出与 pyramid.request.Request.authenticated_userid

如果您尝试在使用传统身份验证和授权策略的应用程序中使用新API,则需要注意一些问题:

升级会话序列化

在……里面 Pyramid 2.0The pyramid.interfaces.ISession 接口进行了更改,要求会话实现只需要支持JSON可序列化的数据类型。与之前要求所有对象都是可拾取的相比,这是一个更严格的合同,而且这样做是出于安全目的。这是一个向后不兼容的更改。以前,如果客户端会话实现受到威胁,应用程序很容易受到远程代码执行攻击,使用精心编制的会话在反序列化时执行代码。

如果需要有关这些更改的详细信息,请参考以下票证:

  • 2.0 feature request: Require that sessions are JSON serializable #2709 <https://github.com/pylons/pyramid/issues/2709> _.

  • deprecate pickleable sessions, recommend json #3353 <https://github.com/pylons/pyramid/pull/3353> _.

  • change to use JSONSerializer for SignedCookieSessionFactory #3413 <https://github.com/pylons/pyramid/pull/3413> _.

对于有兼容性问题的用户,可以创建一个可以同时处理两种格式的序列化程序,直到您满意客户端有时间进行合理升级为止。请记住,会话应该是短暂的,因此受影响的客户端数量应该很小(最长不超过一个身份验证令牌)。示例序列化程序:

 1import pickle
 2from pyramid.session import JSONSerializer
 3from pyramid.session import SignedCookieSessionFactory
 4
 5
 6class JSONSerializerWithPickleFallback(object):
 7    def __init__(self):
 8        self.json = JSONSerializer()
 9
10    def dumps(self, appstruct):
11        """
12        Accept a Python object and return bytes.
13
14        During a migration, you may want to catch serialization errors here,
15        and keep using pickle while finding spots in your app that are not
16        storing JSON-serializable objects. You may also want to integrate
17        a fall-back to pickle serialization here as well.
18        """
19        return self.json.dumps(appstruct)
20
21    def loads(self, bstruct):
22        """Accept bytes and return a Python object."""
23        try:
24            return self.json.loads(bstruct)
25        except ValueError:
26            try:
27                return pickle.loads(bstruct)
28            except Exception:
29                # this block should catch at least:
30                # ValueError, AttributeError, ImportError; but more to be safe
31                raise ValueError
32
33# somewhere in your configuration code
34serializer = JSONSerializerWithPickleFallback()
35session_factory = SignedCookieSessionFactory(..., serializer=serializer)
36config.set_session_factory(session_factory)