diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-09-06 03:36:59 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-09-06 03:36:59 +0000 |
| commit | d66bfb5d1f1aef5cce4941b49740dbd136c95605 (patch) | |
| tree | 0204e233befdb8ad99332e498308468015f3558e | |
| parent | 50df953770409dc9c9558c77bd5c0bbb17ac54f6 (diff) | |
| download | pyramid-d66bfb5d1f1aef5cce4941b49740dbd136c95605.tar.gz pyramid-d66bfb5d1f1aef5cce4941b49740dbd136c95605.tar.bz2 pyramid-d66bfb5d1f1aef5cce4941b49740dbd136c95605.zip | |
Merge multiview2 branch to HEAD.
24 files changed, 2869 insertions, 1774 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index fc86ac5b6..5dd17f4a4 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,117 @@ Next release ============ +- The ``route`` ZCML directive now honors ``view_request_method``, + ``view_request_param`` and ``view_containment`` attributes, which + pass along these values to the associated view if any is provided. + Additionally, the ``request_type`` attribute can now be spelled as + ``view_request_type``, and ``permission`` can be spelled as + ``view_permission``. Any attribute which starts with ``view_`` can + now be spelled without the ``view_`` prefix, so ``view_for`` can be + spelled as ``for`` now, etc. Both forms are documented in the + urldispatch narraitve documentation chapter. + +- The ``request_param`` ZCML view directive attribute (and its + ``bfg_view`` decorator cousin) can now specify both a key and a + value. For example, ``request_param="foo=123"`` means that the foo + key must have a value of ``123`` for the view to "match". + +- Bugfix: the ``discriminator`` for the ZCML "route" directive was + incorrect. It was possible to register two routes that collided + without the system spitting out a ConfigurationConflictError at + startup time. + +- The ``static`` ZCML directive now uses a custom root factory when + constructing a route. + +- The ordering of route declarations vs. the ordering of view + declarations that use a "route_name" in ZCML no longer matters. + Previously it had been impossible to use a route_name from a route + that had not yet been defined in ZCML (order-wise) within a "view" + declaration. + +- The ``@bfg_view`` decorator now accepts three additional arguments: + ``request_method``, ``request_param``, and ``containment``. + ``request_method`` is used when you'd like the view to match only a + request with a particular HTTP ``REQUEST_METHOD``; a string naming + the ``REQUEST_METHOD`` can also be supplied as ``request_type`` for + backwards compatibility. ``request_param`` is used when you'd like + a view to match only a request that contains a particular + ``request.params`` key (with or without a value). ``containment`` + is used when you'd like to match a request that has a context that + has some class or interface in its graph lineage. These are + collectively known as "view predicates". + +- The interface ``IRequestFactories`` was removed from the + repoze.bfg.interfaces module. This interface was never an API. + +- The interfaces ``IPOSTRequest``, ``IGETRequest``, ``IPUTRequest``, + ``IDELETERequest``, and ``IHEADRequest`` have been removed from the + ``repoze.bfg.interfaces`` module. These were not documented as APIs + post-1.0. Instead of using one of these, use a ``request_method`` + ZCML attribute or ``request_method`` bfg_view decorator parameter + containing an HTTP method name (one of ``GET``, ``POST``, ``HEAD``, + ``PUT``, ``DELETE``) instead of one of these interfaces if you were + using one explicitly. Passing a string in the set (``GET``, + ``HEAD``, ``PUT``, ``POST``, ``DELETE``) as a ``request_type`` + argument will work too. Rationale: instead of relying on interfaces + attached to the request object, BFG now uses a "view predicate" to + determine the request type. + +- The function named ``named_request_factories`` and the data + structure named ``DEFAULT_REQUEST_FACTORIES`` have been removed from + the ``repoze.bfg.request`` module. These were never APIs. + +- Feature addition: view predicates. These are exposed as the + ``request_method``, ``request_param``, and ``containment`` + attributes of a ZCML ``view`` declaration, or the respective + arguments to a ``@bfg_view`` decorator. View predicates can be used + to register a view for a more precise set of environment parameters + than was previously possible. For example, you can register two + views with the same ``name`` with different ``request_param`` + attributes. If the ``request.params`` dict contains 'foo' + (request_param="foo"), one view might be called; if it contains + 'bar' (request_param="bar"), another view might be called. + ``request_param`` can also name a key/value pair ala ``foo=123``. + This will match only when the ``foo`` key is in the request.params + dict and it has the value '123'. This particular example makes it + possible to write separate view functions for different form + submissions. The other predicates, ``containment`` and + ``request_method`` work similarly. ``containment`` is a view + predicate that will match only when the context's graph lineage has + an object possessing a particular class or interface, for example. + ``request_method`` is a view predicate that will match when the HTTP + ``REQUEST_METHOD`` equals some string (eg. 'POST'). + +- The repoze.bfg router now catches both + ``repoze.bfg.security.Unauthorized`` and + ``repoze.bfg.view.NotFound`` exceptions while rendering a view. + When the router catches an ``Unauthorized``, it returns the + registered forbidden view. When the router catches a ``NotFound``, + it returns the registered notfound view. + +- Custom ZCML directives which register an authentication or + authorization policy (ala "authtktauthenticationpolicy" or + "aclauthorizationpolicy") should register the policy "eagerly" in + the ZCML directive instead of from within a ZCML action. If an + authentication or authorization policy is not found in the component + registry by the view machinery during deferred ZCML processing, view + security will not work as expected. + +- The API ``repoze.bfg.testing.registerViewPermission`` has been + deprecated. + +- The API ``repoze.bfg.testing.registerView`` now takes a + ``permission`` argument. Use this instead of using + ``repoze.bfg.testing.registerViewPermission``. + +- Removed ViewPermissionFactory from ``repoze.bfg.security``. View + permission checking is now done by registering and looking up an + ISecuredView. + +- Views registered without the help of the ZCML ``view`` directive are + now responsible for performing their own authorization checking. + - The ``repoze.bfg.view.static`` class now accepts a string as its first argument ("root_dir") that represents a package-relative name e.g. ``somepackage:foo/bar/static``. This is now the preferred @@ -975,12 +1086,12 @@ Features policy defaults to an authorization implementation that uses ACLs (``repoze.bfg.authorization.ACLAuthorizationPolicy``). - .. note:: we no longer encourage configuration of "security - policies" using ZCML, as previously we did for - ``ISecurityPolicy``. This is because it's not uncommon to need to - configure settings for concrete authorization or authentication - policies using paste .ini parameters; the app entry point for your - application is the natural place to do this. + We no longer encourage configuration of "security policies" using + ZCML, as previously we did for ``ISecurityPolicy``. This is because + it's not uncommon to need to configure settings for concrete + authorization or authentication policies using paste .ini + parameters; the app entry point for your application is the natural + place to do this. - Two new abstractions have been added in the way of adapters used by the system: an ``IAuthorizationPolicy`` and an @@ -1387,9 +1498,8 @@ Features source at installation time. In particular, ``repoze.bfg`` no longer depends on the ``lxml`` package. - .. note:: this change has introduced some backwards - incompatibilities, described in the "Backwards - Incompatibilities" section below. + This change has introduced some backwards incompatibilities, + described in the "Backwards Incompatibilities" section below. - This release was tested on Windows XP. It appears to work fine and all the tests pass. @@ -1565,7 +1675,7 @@ Backwards Incompatibilities opposed to URL-dispatch), and the root object supplied the``repoze.bfg.interfaces.ILocation`` interface, but the children returned via its ``__getitem__`` returned an object that did not - implement the same interface, :mod:`repoze.bfg` provided some + implement the same interface, ``repoze.bfg`` provided some implicit help during traversal. This traversal feature wrapped subobjects from the root (and thereafter) that did not implement ``ILocation`` in proxies which automatically provided them with a @@ -1815,7 +1925,7 @@ Features - "Virtual root" support for traversal-based applications has been added. Virtual root support is useful when you'd like to host some - model in a :mod:`repoze.bfg` model graph as an application under a + model in a ``repoze.bfg`` model graph as an application under a URL pathname that does not include the model path itself. For more information, see the (new) "Virtual Hosting" chapter in the documentation. @@ -1854,7 +1964,7 @@ Features -------- - You can now override the NotFound and Unauthorized responses that - :mod:`repoze.bfg` generates when a view cannot be found or cannot be + ``repoze.bfg`` generates when a view cannot be found or cannot be invoked due to lack of permission. See the "ZCML Hooks" chapter in the docs for more information. diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst index 48b2eedfc..34f606a97 100644 --- a/docs/narr/hybrid.rst +++ b/docs/narr/hybrid.rst @@ -259,9 +259,9 @@ application. Let's see what they are. "Global" Views Match Any Route When A More Specific View Doesn't ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Note that views that *don't* mention a ``route_name`` will *also* -match when *any* route matches. For example, the "bazbuz" view below -will be found if the route named "abc" below is matched. +Note that views that don't mention a ``route_name`` will *also* match +when *any* route matches. For example, the "bazbuz" view below will +be found if the route named "abc" below is matched. .. code-block:: xml @@ -323,7 +323,7 @@ name` to try to locate a view callable. A view is registered for a ``route`` either as its default view via the ``view=`` attribute of a ``route`` declaration in ZCML *or* as a standalone ``<view>`` declaration (or via the ``@bfg_route`` -decorator) which has a ``route_name`` that matches the route's name). +decorator) which has a ``route_name`` that matches the route's name. At startup time, when such a registration is encountered, the view is registered for the ``context`` type ``None`` (meaning *any* context) and a *special* request type which is dynamically generated. This @@ -463,9 +463,3 @@ statement ordering is very important, because routes are evaluated in a specific order, unlike traversal, which depends on emergent behavior rather than an ordered list of directives. -A ``<route>`` Statement *Must* Precede Any ``<view>`` Statement Which Mentions It -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A ``<route>`` declaration *must* precede (in XML order) any ``<view>`` -declaration which names it as a ``route_name``. If it does not, at -application startup time a ``ConfigurationError`` will be raised. diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index fdaea228f..dfae6e1b0 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -62,15 +62,22 @@ attributes are optional unless the description names them as required. path - The `route path - <http://routes.groovie.org/manual.html#route-path>`_, - e.g. ``ideas/:idea``. This attribute is required. + The path of the route e.g. ``ideas/:idea``. This attribute is + required. See :ref:`route_path_pattern_syntax` for information + about the syntax of route paths. name - The `route name - <http://routes.groovie.org/manual.html#route-name>`_, - e.g. ``myroute``. This attribute is required. + The name of the route, e.g. ``myroute``. This attribute is + required. It must be unique among all defined routes in a given + configuration. + +factory + + The Python dotted-path name to a function that will generate a + :mod:`repoze.bfg` context object when this route matches. + e.g. ``mypackage.models.MyFactoryClass``. If this argument is not + specified, a default root factory will be used. view @@ -86,23 +93,79 @@ view_for attribute is used. If this attribute is not specified, the default (``None``) will be used. -permission + If the ``view`` attribute is not provided, this attribute has no + effect. - The permission name required to invoke the view. - e.g. ``edit``. (see :ref:`using_security_with_urldispatch` for more - information about permissions). + This attribute can also be spelled as ``for``. -factory +view_permission - The Python dotted-path name to a function that will generate a - :mod:`repoze.bfg` context object when this route matches. - e.g. ``mypackage.models.MyFactoryClass``. If this argument is not - specified, a default root factory will be used. + The permission name required to invoke the view associated with this + route. e.g. ``edit``. (see :ref:`using_security_with_urldispatch` + for more information about permissions). + + If the ``view`` attribute is not provided, this attribute has no + effect. + + This atribute can also be spelled as ``permission``. + +view_request_type -request_type + A dotted Python name to an interface representing a :term:`request + type`. For backwards compatibility with :mod:`repoze.bfg` 1.0 and + before, this may also be a string naming an HTTP ``REQUEST_METHOD`` + (any of ``GET``, ``POST``, ``HEAD``, ``DELETE``, ``PUT``). However, + these values should really be specified in ``request_method``. If + this argument is not specified, any request type will be considered + a match for the view associated with this route. - A string representing an HTTP method name, e.g. ``GET`` or ``POST`` - or an interface representing a :term:`request type`. + If the ``view`` attribute is not provided, this attribute has no + effect. + + This attribute can also be spelled as ``request_type``. + +view_request_method + + A string representing an HTTP method name, e.g. ``GET``, ``POST``, + ``HEAD``, ``DELETE``, ``PUT``. If this argument is not specified + any request method will be considered a match for the view + associated with this route. + + If the ``view`` attribute is not provided, this attribute has no + effect. + + This attribute can also be spelled as ``request_method``. + +view_request_param + + This value can be any string. A view declaration with this + attribute ensures that the associated view will only be called when + the request has a key in the ``request.params`` dictionary (an HTTP + ``GET`` or ``POST`` variable) that has a name which matches the + supplied value. If the value supplied to the attribute has a ``=`` + sign in it, e.g. ``request_params="foo=123"``, then the key + (``foo``) must both exist in the ``request.params`` dictionary, and + the value must match the right hand side of the expression (``123``) + for the view to "match" the current request. + + If the ``view`` attribute is not provided, this attribute has no + effect. + + This attribute can also be spelled as ``request_param``. + +view_containment + + This value should be a Python dotted-path string representing the + class that a graph traversal parent object of the :term:`context` + must be an instance of (or :term:`interface` that a parent object + must provide) in order for this view to be found and called. Your + models must be "location-aware" to use this feature. See + :ref:`location_aware` for more information about location-awareness. + + If the ``view`` attribute is not provided, this attribute has no + effect. + + This attribute can also be spelled as ``containment``. The Matchdict ------------- @@ -114,6 +177,8 @@ request named ``matchdict`` with the values that match patterns in the ``path`` element. If the URL pattern does not match, no matchdict is generated. +.. _route_path_pattern_syntax: + Path Pattern Syntax -------------------- diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 389c4ddfc..28fe643d8 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -247,14 +247,34 @@ permission call the view. See :ref:`view_security_section` for more information about view security and permissions. -request_type +request_method This value can either be one of the strings 'GET', 'POST', 'PUT', - 'DELETE', or 'HEAD' representing an HTTP method, *or* it may be - Python dotted-path string representing the :term:`interface` that - the :term:`request` must have in order for this view to be found and - called. See :ref:`view_request_types_section` for more information - about request types. + 'DELETE', or 'HEAD' representing an HTTP ``REQUEST_METHOD``. A view + declaration with this attribute ensures that the view will only be + called when the request's ``method`` (aka ``REQUEST_METHOD``) string + matches the supplied value. + +request_param + + This value can be any string. A view declaration with this + attribute ensures that the view will only be called when the request + has a key in the ``request.params`` dictionary (an HTTP ``GET`` or + ``POST`` variable) that has a name which matches the supplied value. + If the value supplied to the attribute has a ``=`` sign in it, + e.g. ``request_params="foo=123"``, then the key (``foo``) must both + exist in the ``request.params`` dictionary, and the value must match + the right hand side of the expression (``123``) for the view to + "match" the current request. + +containment + + This value should be a Python dotted-path string representing the + class that a graph traversal parent object of the :term:`context` + must be an instance of (or :term:`interface` that a parent object + must provide) in order for this view to be found and called. Your + models must be "location-aware" to use this feature. See + :ref:`location_aware` for more information about location-awareness. route_name @@ -271,6 +291,69 @@ route_name :term:`root factory`. See :ref:`hybrid_chapter` for more information on using this advanced feature. +request_type + + This value should be a Python dotted-path string representing the + :term:`interface` that the :term:`request` must have in order for + this view to be found and called. See + :ref:`view_request_types_section` for more information about request + types. For backwards compatibility with :mod:`repoze.bfg` version + 1.0, this value may also be an HTTP ``REQUEST_METHOD`` string, e.g. + ('GET', 'HEAD', 'PUT', 'POST', or 'DELETE'). Passing request method + strings as a ``request_type`` is deprecated. Use the + ``request_method`` attribute instead for maximum forward + compatibility. + +.. _view_lookup_ordering: + +View Lookup Ordering +-------------------- + +Attributes of the ZCML ``view`` directive can be thought of like +"narrowers" or "predicates". In general, the greater number of +attributes possessed by a view directive, the more specific the +circumstances need to be before the registered view will be called. + +For any given request, a view with five predicates will always be +found and evaluated before a view with two, for example. All +predicatese must match for the associated view to be called. + +This does not mean however, that :mod:`repoze.bfg` "stops looking" +when it finds a view registration with predicates that don't match. +If one set of view predicates does not match, the "next most specific" +view (if any) view is consulted for predicates, and so on, until a +view is found, or no view can be matched up with the request. The +first view with a set of predicates all of which match the request +environment will be invoked. + +If no view can be found which has predicates which allow it to be +matched up with the request, :mod:`repoze.bfg` will return an error to +the user's browser, representing a "not found" (404) page. See +:ref:`changing_the_notfound_view` for more information about changing +the default notfound view. + +There are a several exceptions to the the rule which says that ZCML +directive attributes represent "narrowings". Several attributes of +the ``view`` directive are *not* narrowing predicates. These are +``permission`` and ``name``. + +The value of the ``permission`` attribute represents the permission +that must be possessed by the user to invoke any found view. When a +view is found that matches all predicates, but the invoking user does +not possess the permission implied by any associated ``permission`` in +the current context, processing stops, and an ``Unauthorized`` error +is raised, usually resulting in a "forbidden" view being shown to the +invoking user. No further view narrowing or view lookup is done. + +.. note:: + + See :ref:`changing_the_forbidden_view` for more information about + changing the default forbidden view. + +The value of the ``name`` attribute represents a direct match of the +view name returned via traversal. It is part of intial view lookup +rather than a predicate/narrower. + .. _mapping_views_to_urls_using_a_decorator_section: Mapping Views to URLs Using a Decorator @@ -281,7 +364,8 @@ more comfortable defining your view declarations using Python, you may use the ``repoze.bfg.view.bfg_view`` decorator to associate your view functions with URLs instead of using :term:`ZCML` for the same purpose. ``repoze.bfg.view.bfg_view`` can be used to associate -``for``, ``name``, ``permission`` and ``request_type`` information -- +``for``, ``name``, ``permission`` and ``request_method``, +``containment``, ``request_param`` and ``request_type`` information -- as done via the equivalent ZCML -- with a function that acts as a :mod:`repoze.bfg` view. @@ -343,8 +427,9 @@ All arguments to ``bfg_view`` are optional. If ``name`` is not supplied, the empty string is used (implying the default view). -If ``request_type`` is not supplied, the interface ``None`` is used, -implying any request type. +If ``request_type`` is not supplied, the value ``None`` is used, +implying any request type. Otherwise, this should be a class or +interface. If ``for_`` is not supplied, the interface ``zope.interface.Interface`` (which matches any model) is used. @@ -357,6 +442,21 @@ If ``route_name`` is supplied, the view will be invoked only if the named route matches. *This is an advanced feature, not often used by "civilians"*. +If ``request_method`` is supplied, the view will be invoked only if +the ``REQUEST_METHOD`` of the request matches the value. + +If ``request_param`` is supplied, the view will be invoked only if the +``request.params`` data structure contains a key matching the value +provided. + +If ``containment`` is supplied, the view will be invoked only if a +location parent supplies the interface or class implied by the +provided value. + +View lookup ordering for views registered with the ``bfg_view`` +decorator is the same as for those registered via ZCML. See +:ref:`view_lookup_ordering` for more information. + All arguments may be omitted. For example: .. code-block:: python @@ -371,8 +471,8 @@ All arguments may be omitted. For example: Such a registration as the one directly above implies that the view name will be ``my_view``, registered ``for_`` any model type, using no -permission, registered against requests which implement any request -method or interface. +permission, registered against requests with any request method / +request type / request param / route name / containment. If your view callable is a class, the ``bfg_view`` decorator can also be used as a class decorator in Python 2.6 and better (Python 2.5 and @@ -561,9 +661,10 @@ Custom View Request Types You can make use of *custom* view request types by attaching an :term:`interface` to the request and specifying this interface in the -``request_type`` parameter. For example, you might want to make use -of simple "content negotiation", only invoking a particular view if -the request has a content-type of 'application/json'. +``request_type`` parameter as a dotted Python name. For example, you +might want to make use of simple "content negotiation", only invoking +a particular view if the request has a content-type of +'application/json'. For information about using interface to specify a request type, see :ref:`using_an_event_to_vary_the_request_type`. diff --git a/repoze/bfg/interfaces.py b/repoze/bfg/interfaces.py index 779c0ef03..6a999a98c 100644 --- a/repoze/bfg/interfaces.py +++ b/repoze/bfg/interfaces.py @@ -6,20 +6,9 @@ from zope.component.interfaces import IObjectEvent class IRequest(Interface): """ Request type interface attached to all request objects """ -class IPOSTRequest(IRequest): - """ Request type interface attached to POST requests""" - -class IGETRequest(IRequest): - """ Request type interface attached to GET requests""" - -class IPUTRequest(IRequest): - """ Request type interface attached to PUT requests""" - -class IDELETERequest(IRequest): - """ Request type interface attached to DELETE requests""" - -class IHEADRequest(IRequest): - """ Request type interface attached to HEAD requests""" +class IRouteRequest(IRequest): + """ *internal only* interface used to mark a request when a route + matches. Not an API.""" class IResponseFactory(Interface): """ A utility which generates a response factory """ @@ -36,7 +25,25 @@ class IResponse(Interface): class IView(Interface): def __call__(context, request): - """ Must return an object that implements IResponse """ + """ Must return an object that implements IResponse. May + optionally raise ``repoze.bfg.security.Unauthorized`` if an + authorization failure is detected during view execution.""" + +class ISecuredView(IView): + """ Internal interface. Not an API. """ + def __call_permissive__(context, request): + """ Guaranteed-permissive version of __call__ """ + + def __permitted__(context, request): + """ Return True if view execution will be permitted using the + context and request, False otherwise""" + +class IMultiView(ISecuredView): + """ *internal only*. A multiview is a secured view that is a + collection of other views. Each of the views is associated with + zero or more predicates. Not an API.""" + def add(view, predicates, score): + """ Add a view to the multiview. """ class IRootFactory(Interface): def __call__(environ): @@ -227,10 +234,6 @@ class IAuthorizationPolicy(Interface): def principals_allowed_by_permission(context, permission): """ Return a set of principal identifiers allowed by the permission """ -class IRequestFactories(Interface): - """ Marker utility interface representing a dictionary of request - factory descriptions""" - class IPackageOverrides(Interface): """ Utility for pkg_resources overrides """ diff --git a/repoze/bfg/paster_templates/alchemy/+package+/configure.zcml b/repoze/bfg/paster_templates/alchemy/+package+/configure.zcml index 2ce917de8..418d04419 100644 --- a/repoze/bfg/paster_templates/alchemy/+package+/configure.zcml +++ b/repoze/bfg/paster_templates/alchemy/+package+/configure.zcml @@ -9,16 +9,15 @@ /> <view - for=".models.MyApp" - view=".views.static_view" - name="static" - /> - - <view for=".models.MyModel" view=".views.view_model" /> + <static + name="static" + path="templates/static" + /> + <subscriber for="repoze.bfg.interfaces.INewRequest" handler=".run.handle_teardown" /> diff --git a/repoze/bfg/paster_templates/alchemy/+package+/views.py_tmpl b/repoze/bfg/paster_templates/alchemy/+package+/views.py_tmpl index f34868ad1..2b176fb0d 100644 --- a/repoze/bfg/paster_templates/alchemy/+package+/views.py_tmpl +++ b/repoze/bfg/paster_templates/alchemy/+package+/views.py_tmpl @@ -1,7 +1,4 @@ from repoze.bfg.chameleon_zpt import render_template_to_response -from repoze.bfg.view import static - -static_view = static('templates/static') def view_root(context, request): return render_template_to_response('templates/root.pt', diff --git a/repoze/bfg/paster_templates/routesalchemy/+package+/configure.zcml b/repoze/bfg/paster_templates/routesalchemy/+package+/configure.zcml index 14bb95cd6..f96a73ecf 100644 --- a/repoze/bfg/paster_templates/routesalchemy/+package+/configure.zcml +++ b/repoze/bfg/paster_templates/routesalchemy/+package+/configure.zcml @@ -12,10 +12,9 @@ view=".views.my_view" /> - <route - path="/static/*subpath" + <static name="static" - view=".views.static_view" + path="templates/static" /> </configure> diff --git a/repoze/bfg/paster_templates/routesalchemy/+package+/views.py_tmpl b/repoze/bfg/paster_templates/routesalchemy/+package+/views.py_tmpl index c95b215ff..4b862ba61 100644 --- a/repoze/bfg/paster_templates/routesalchemy/+package+/views.py_tmpl +++ b/repoze/bfg/paster_templates/routesalchemy/+package+/views.py_tmpl @@ -1,11 +1,8 @@ from repoze.bfg.chameleon_zpt import render_template_to_response -from repoze.bfg.view import static from {{package}}.models import DBSession from {{package}}.models import Model -static_view = static('templates/static') - def my_view(request): dbsession = DBSession() root = dbsession.query(Model).filter(Model.name==u'root').first() diff --git a/repoze/bfg/paster_templates/zodb/+package+/configure.zcml b/repoze/bfg/paster_templates/zodb/+package+/configure.zcml index 89bf74525..aa98296c5 100644 --- a/repoze/bfg/paster_templates/zodb/+package+/configure.zcml +++ b/repoze/bfg/paster_templates/zodb/+package+/configure.zcml @@ -8,10 +8,9 @@ view=".views.my_view" /> - <view - for=".models.MyModel" - view=".views.static_view" + <static name="static" + path="templates/static" /> </configure> diff --git a/repoze/bfg/paster_templates/zodb/+package+/views.py_tmpl b/repoze/bfg/paster_templates/zodb/+package+/views.py_tmpl index 9c5fe7a67..d9f0df378 100644 --- a/repoze/bfg/paster_templates/zodb/+package+/views.py_tmpl +++ b/repoze/bfg/paster_templates/zodb/+package+/views.py_tmpl @@ -1,7 +1,4 @@ from repoze.bfg.chameleon_zpt import render_template_to_response -from repoze.bfg.view import static - -static_view = static('templates/static') def my_view(context, request): return render_template_to_response('templates/mytemplate.pt', diff --git a/repoze/bfg/request.py b/repoze/bfg/request.py index 932789e0a..9b29d3e1f 100644 --- a/repoze/bfg/request.py +++ b/repoze/bfg/request.py @@ -1,36 +1,14 @@ -from zope.component import getUtility from zope.interface import implements -from webob import Request as WebobRequest - -from zope.deprecation import deprecated from zope.interface.interface import InterfaceClass -from repoze.bfg.interfaces import IRequest -from repoze.bfg.interfaces import IGETRequest -from repoze.bfg.interfaces import IPOSTRequest -from repoze.bfg.interfaces import IPUTRequest -from repoze.bfg.interfaces import IDELETERequest -from repoze.bfg.interfaces import IHEADRequest -from repoze.bfg.interfaces import IRequestFactories +from zope.component import queryUtility -def request_factory(environ): - try: - method = environ['REQUEST_METHOD'] - except KeyError: - method = None - - if 'bfg.routes.route' in environ: - route = environ['bfg.routes.route'] - request_factories = getUtility(IRequestFactories, name=route.name or '') - else: - request_factories = DEFAULT_REQUEST_FACTORIES +from webob import Request as WebobRequest - try: - request_factory = request_factories[method]['factory'] - except KeyError: - request_factory = request_factories[None]['factory'] +from zope.deprecation import deprecated - return request_factory(environ) +from repoze.bfg.interfaces import IRequest +from repoze.bfg.interfaces import IRouteRequest def make_request_ascii(event): """ An event handler that causes the request charset to be ASCII; @@ -39,75 +17,26 @@ def make_request_ascii(event): request = event.request request.charset = None -def named_request_factories(name=None): - # We use 'precooked' Request subclasses that correspond to HTTP - # request methods when returning a request object from - # ``request_factory`` rather than using ``alsoProvides`` to attach - # the proper interface to an unsubclassed webob.Request. This - # pattern is purely an optimization (e.g. preventing calls to - # ``alsoProvides`` means the difference between 590 r/s and 690 - # r/s on a MacBook 2GHz). This method should be never imported - # directly by user code; it is *not* an API. - if name is None: - default_iface = IRequest - get_iface = IGETRequest - post_iface = IPOSTRequest - put_iface = IPUTRequest - delete_iface = IDELETERequest - head_iface = IHEADRequest - else: - IC = InterfaceClass - default_iface = IC('%s_IRequest' % name, (IRequest,)) - get_iface = IC('%s_IGETRequest' % name, (default_iface, IGETRequest)) - post_iface = IC('%s_IPOSTRequest' % name, (default_iface, IPOSTRequest)) - put_iface = IC('%s_IPUTRequest' % name, (default_iface, IPUTRequest)) - delete_iface = IC('%s_IDELETERequest' % name, (default_iface, - IDELETERequest)) - head_iface = IC('%s_IHEADRequest' % name, (default_iface, - IHEADRequest,)) - - class Request(WebobRequest): - implements(default_iface) - charset = 'utf-8' +class Request(WebobRequest): + implements(IRequest) + charset = 'utf-8' - class GETRequest(WebobRequest): - implements(get_iface) - charset = 'utf-8' - - class POSTRequest(WebobRequest): - implements(post_iface) - charset = 'utf-8' - - class PUTRequest(WebobRequest): - implements(put_iface) - charset = 'utf-8' - - class DELETERequest(WebobRequest): - implements(delete_iface) - charset = 'utf-8' +def request_factory(environ): + if 'bfg.routes.route' in environ: + route = environ['bfg.routes.route'] + factory = queryUtility(IRouteRequest, name=route.name) + if factory is not None: + return factory(environ) + return Request(environ) - class HEADRequest(WebobRequest): - implements(head_iface) +def create_route_request_factory(name): + iface = InterfaceClass('%s_IRequest' % name, (IRouteRequest,)) + + class RouteRequest(WebobRequest): + implements(iface) charset = 'utf-8' - factories = { - IRequest:{'interface':default_iface, 'factory':Request}, - IGETRequest:{'interface':get_iface, 'factory':GETRequest}, - IPOSTRequest:{'interface':post_iface, 'factory':POSTRequest}, - IPUTRequest:{'interface':put_iface, 'factory':PUTRequest}, - IDELETERequest:{'interface':delete_iface, 'factory':DELETERequest}, - IHEADRequest:{'interface':head_iface, 'factory':HEADRequest}, - None:{'interface':default_iface, 'factory':Request}, - 'GET':{'interface':get_iface, 'factory':GETRequest}, - 'POST':{'interface':post_iface, 'factory':POSTRequest}, - 'PUT':{'interface':put_iface, 'factory':PUTRequest}, - 'DELETE':{'interface':delete_iface, 'factory':DELETERequest}, - 'HEAD':{'interface':head_iface, 'factory':HEADRequest}, - } - - return factories - -DEFAULT_REQUEST_FACTORIES = named_request_factories() + return RouteRequest from repoze.bfg.threadlocal import get_current_request as get_request # b/c diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py index 346e8f42f..d9502a435 100644 --- a/repoze/bfg/router.py +++ b/repoze/bfg/router.py @@ -12,18 +12,17 @@ from repoze.bfg.events import NewRequest from repoze.bfg.events import NewResponse from repoze.bfg.events import WSGIApplicationCreatedEvent +from repoze.bfg.interfaces import IAuthorizationPolicy +from repoze.bfg.interfaces import IAuthenticationPolicy +from repoze.bfg.interfaces import IDefaultRootFactory +from repoze.bfg.interfaces import IForbiddenView from repoze.bfg.interfaces import ILogger +from repoze.bfg.interfaces import INotFoundView from repoze.bfg.interfaces import IRootFactory from repoze.bfg.interfaces import IRouter from repoze.bfg.interfaces import IRoutesMapper from repoze.bfg.interfaces import ISettings -from repoze.bfg.interfaces import IForbiddenView -from repoze.bfg.interfaces import INotFoundView from repoze.bfg.interfaces import IView -from repoze.bfg.interfaces import IViewPermission -from repoze.bfg.interfaces import IAuthorizationPolicy -from repoze.bfg.interfaces import IAuthenticationPolicy -from repoze.bfg.interfaces import IDefaultRootFactory from repoze.bfg.log import make_stream_logger @@ -32,7 +31,7 @@ from repoze.bfg.registry import populateRegistry from repoze.bfg.request import request_factory -from repoze.bfg.security import Allowed +from repoze.bfg.security import Unauthorized from repoze.bfg.settings import Settings from repoze.bfg.settings import get_options @@ -43,8 +42,9 @@ from repoze.bfg.traversal import _traverse from repoze.bfg.urldispatch import RoutesRootFactory -from repoze.bfg.view import default_forbidden_view from repoze.bfg.view import default_notfound_view +from repoze.bfg.view import default_forbidden_view +from repoze.bfg.view import NotFound _marker = object() @@ -52,22 +52,19 @@ class Router(object): """ The main repoze.bfg WSGI application. """ implements(IRouter) - debug_authorization = False debug_notfound = False threadlocal_manager = manager def __init__(self, registry): self.registry = registry self.logger = registry.queryUtility(ILogger, 'repoze.bfg.debug') - self.forbidden_view = registry.queryUtility( - IForbiddenView, default=default_forbidden_view) self.notfound_view = registry.queryUtility( INotFoundView, default=default_notfound_view) + self.forbidden_view = registry.queryUtility( + IForbiddenView, default=default_forbidden_view) settings = registry.queryUtility(ISettings) if settings is not None: - self.debug_authorization = settings.debug_authorization self.debug_notfound = settings.debug_notfound - self.secured = not not registry.queryUtility(IAuthenticationPolicy) self.root_factory = registry.queryUtility(IRootFactory, default=DefaultRootFactory) self.root_policy = self.root_factory # b/w compat @@ -120,44 +117,10 @@ class Router(object): 'Non-response object returned from view %s: %r' % (view_name, response)) - if self.secured: - permitted = registry.queryMultiAdapter((context, request), - IViewPermission, - name=view_name) - if permitted is None: - if self.debug_authorization: - permitted = Allowed( - 'Allowed: view name %r against context %r (no ' - 'permission registered).' % (view_name, context)) - else: - permitted = True - - else: - if self.debug_authorization: - permitted = Allowed( - 'Allowed: view name %r against context %r (no ' - 'authentication policy in use).' % (view_name, context)) - else: - permitted = True - - if self.debug_authorization: - logger and logger.debug( - 'debug_authorization of url %s (view name %r against ' - 'context %r): %s' % (request.url, view_name, context, - permitted)) - - if not permitted: - if self.debug_authorization: - msg = str(permitted) - else: - msg = 'Unauthorized: failed security policy check' - environ['repoze.bfg.message'] = msg - return respond(self.forbidden_view(context, request), - '<IForbiddenView>') - + provides = map(providedBy, (context, request)) view_callable = registry.adapters.lookup( - map(providedBy, (context, request)), IView, name=view_name, - default=None) + provides, IView, name=view_name, default=None) + if view_callable is None: if self.debug_notfound: @@ -175,7 +138,19 @@ class Router(object): return respond(self.notfound_view(context, request), '<INotFoundView>') - response = view_callable(context, request) + try: + response = view_callable(context, request) + except Unauthorized, why: + msg = why[0] + environ = getattr(request, 'environ', {}) + environ['repoze.bfg.message'] = msg + response = self.forbidden_view(context, request) + except NotFound, why: + msg = why[0] + environ = getattr(request, 'environ', {}) + environ['repoze.bfg.message'] = msg + response = self.notfound_view(context, request) + return respond(response, view_name) finally: diff --git a/repoze/bfg/security.py b/repoze/bfg/security.py index c11fea0b6..bb33435a0 100644 --- a/repoze/bfg/security.py +++ b/repoze/bfg/security.py @@ -1,14 +1,11 @@ -from zope.component import queryMultiAdapter +from zope.component import getSiteManager from zope.component import queryUtility +from zope.component import providedBy from zope.deprecation import deprecated -from zope.interface import implements -from zope.interface import classProvides - from repoze.bfg.interfaces import IAuthenticationPolicy from repoze.bfg.interfaces import IAuthorizationPolicy -from repoze.bfg.interfaces import IViewPermission -from repoze.bfg.interfaces import IViewPermissionFactory +from repoze.bfg.interfaces import ISecuredView Everyone = 'system.Everyone' Authenticated = 'system.Authenticated' @@ -95,13 +92,14 @@ def view_execution_permitted(context, request, name=''): ``request``. Return a boolean result. If no authentication policy is in effect, or if the view is not protected by a permission, return True.""" - result = queryMultiAdapter((context, request), IViewPermission, - name=name, default=None) - if result is None: + sm = getSiteManager() + provides = map(providedBy, (context, request)) + view = sm.adapters.lookup(provides, ISecuredView, name=name) + if view is None: return Allowed( 'Allowed: view name %r in context %r (no permission defined)' % (name, context)) - return result + return view.__permitted__(context, request) def remember(request, principal, **kw): """ Return a sequence of header tuples (e.g. ``[('Set-Cookie', @@ -232,16 +230,6 @@ class ACLAllowed(ACLPermitsResult): as the ``msg`` attribute.""" boolval = 1 -class ViewPermissionFactory(object): - classProvides(IViewPermissionFactory) - implements(IViewPermission) - - def __init__(self, permission_name): - self.permission_name = permission_name - - def __call__(self, context, request): - return has_permission(self.permission_name, context, request) - class Unauthorized(Exception): pass diff --git a/repoze/bfg/testing.py b/repoze/bfg/testing.py index eebda54c8..99d3c5e8d 100644 --- a/repoze/bfg/testing.py +++ b/repoze/bfg/testing.py @@ -1,5 +1,7 @@ import copy +from zope.deprecation import deprecated + from zope.interface import Interface from zope.interface import implements @@ -79,21 +81,40 @@ def registerDummyRenderer(path, renderer=None): renderer = DummyTemplateRenderer() return registerUtility(renderer, ITemplateRenderer, path) -def registerView(name, result='', view=None, for_=(Interface, Interface)): +def registerView(name, result='', view=None, for_=(Interface, Interface), + permission=None): """ Registers ``repoze.bfg`` view function under the name ``name``. The view will return a webob Response object with the ``result`` value as its body attribute. To gain more control, if you pass in a non-None ``view``, this view function will be used instead of an automatically generated view function (and - ``result`` is not used). This function is useful when dealing - with code that wants to call, + ``result`` is not used). To protect the view using a permission, + pass in a non-``None`` value as ``permission``. This permission + will be checked by any existing security policy when view + execution is attempted. This function is useful when dealing with + code that wants to call, e.g. ``repoze.bfg.view.render_view_to_response``.""" + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import ISecuredView + from repoze.bfg.security import has_permission + from repoze.bfg.security import Unauthorized if view is None: def view(context, request): from webob import Response return Response(result) - from repoze.bfg.interfaces import IView - return registerAdapter(view, for_, IView, name) + if permission is None: + return registerAdapter(view, for_, IView, name) + else: + def _secure(context, request): + if not has_permission(permission, context, request): + raise Unauthorized('no permission') + else: + return view(context, request) + _secure.__call_permissive__ = view + def permitted(context, request): + return has_permission(permission, context, request) + _secure.__permitted__ = permitted + return registerAdapter(_secure, for_, ISecuredView, name) def registerViewPermission(name, result=True, viewpermission=None, for_=(Interface, Interface)): @@ -108,7 +129,10 @@ def registerViewPermission(name, result=True, viewpermission=None, used). This method is useful when dealing with code that wants to call, e.g. ``repoze.bfg.view.view_execution_permitted``. Note that view permissions are not checked unless a security - policy is in effect (see ``registerSecurityPolicy``).""" + policy is in effect (see ``registerSecurityPolicy``). + + **This function was deprecated in repoze.bfg 1.2.** + """ from repoze.bfg.security import Allowed from repoze.bfg.security import Denied if result is True: @@ -121,6 +145,17 @@ def registerViewPermission(name, result=True, viewpermission=None, from repoze.bfg.interfaces import IViewPermission return registerAdapter(viewpermission, for_, IViewPermission, name) +deprecated('registerViewPermission', + 'registerViewPermission has been deprecated. As of repoze.bfg ' + 'version 1.2, view functions are now responsible for protecting ' + 'their own execution. A call to this function wont prevent a ' + 'view from being executed by the repoze.bfg router, nor ' + 'will the ``repoze.bfg.security.view_execution_permitted`` function ' + 'use the permission registered with this function. Instead,' + 'registering a view permission during testing, use the ' + '``repoze.bfg.testing.registerView`` directive with a ' + '``permission`` argument.') + def registerUtility(impl, iface=Interface, name=''): """ Register a Zope component architecture utility component. This is exposed as a convenience in this package to avoid needing diff --git a/repoze/bfg/tests/test_integration.py b/repoze/bfg/tests/test_integration.py index 14dd6a8ad..681dcf043 100644 --- a/repoze/bfg/tests/test_integration.py +++ b/repoze/bfg/tests/test_integration.py @@ -18,10 +18,6 @@ def wsgiapptest(environ, start_response): """ """ return '123' -def _getRequestInterface(name_or_iface=None): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES[name_or_iface]['interface'] - class WGSIAppPlusBFGViewTests(unittest.TestCase): def setUp(self): cleanUp() @@ -39,7 +35,8 @@ class WGSIAppPlusBFGViewTests(unittest.TestCase): self.assertEqual(result, '123') def test_scanned(self): - IRequest = _getRequestInterface() + from zope.component import getSiteManager + from repoze.bfg.interfaces import IRequest from repoze.bfg.interfaces import IView from repoze.bfg.zcml import scan context = DummyContext() @@ -48,9 +45,11 @@ class WGSIAppPlusBFGViewTests(unittest.TestCase): actions = context.actions self.assertEqual(len(actions), 1) action = actions[0] - self.assertEqual(action['args'], - ('registerAdapter', - wsgiapptest, (INothing, IRequest), IView, '', None)) + register = action['callable'] + register() + sm = getSiteManager() + view = sm.adapters.lookup((INothing, IRequest), IView, name='') + self.assertEqual(view, wsgiapptest) here = os.path.dirname(__file__) staticapp = static(os.path.join(here, 'fixtures')) @@ -101,8 +100,8 @@ class TestGrokkedApp(unittest.TestCase): def test_it(self): import inspect - from repoze.bfg.interfaces import IPOSTRequest from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRequest import repoze.bfg.tests.grokkedapp as package from zope.configuration import config from zope.configuration import xmlconfig @@ -112,37 +111,25 @@ class TestGrokkedApp(unittest.TestCase): xmlconfig.include(context, 'configure.zcml', package) actions = context.actions - post_iface = _getRequestInterface(IPOSTRequest) - request_iface = _getRequestInterface() - postview = actions[-1] self.assertEqual(postview[0][1], None) self.assertEqual(postview[0][2], '') - self.assertEqual(postview[0][3], post_iface) + self.assertEqual(postview[0][3], IRequest) self.assertEqual(postview[0][4], IView) - self.assertEqual(postview[2][1], package.grokked_post) - self.assertEqual(postview[2][2], (None, post_iface)) - self.assertEqual(postview[2][3], IView) klassview = actions[-2] self.assertEqual(klassview[0][1], None) self.assertEqual(klassview[0][2], 'grokked_klass') - self.assertEqual(klassview[0][3], request_iface) + self.assertEqual(klassview[0][3], IRequest) self.assertEqual(klassview[0][4], IView) - self.assertEqual(klassview[2][1], package.grokked_klass) - self.assertEqual(klassview[2][2], (None, request_iface)) - self.assertEqual(klassview[2][3], IView) self.failUnless(inspect.isfunction(package.grokked_klass)) self.assertEqual(package.grokked_klass(None, None), None) funcview = actions[-3] self.assertEqual(funcview[0][1], None) self.assertEqual(funcview[0][2], '') - self.assertEqual(funcview[0][3], request_iface) + self.assertEqual(funcview[0][3], IRequest) self.assertEqual(funcview[0][4], IView) - self.assertEqual(funcview[2][1], package.grokked) - self.assertEqual(funcview[2][2], (None, request_iface)) - self.assertEqual(funcview[2][3], IView) class DummyContext: pass diff --git a/repoze/bfg/tests/test_request.py b/repoze/bfg/tests/test_request.py index 5f61f4efc..8aeeff42d 100644 --- a/repoze/bfg/tests/test_request.py +++ b/repoze/bfg/tests/test_request.py @@ -12,7 +12,7 @@ class TestMakeRequestASCII(unittest.TestCase): self._callFUT(event) self.assertEqual(request.charset, None) -class TestRequestSubclass(object): +class RequestTestBase(object): def _makeOne(self, environ): request = self._getTargetClass()(environ) return request @@ -37,71 +37,22 @@ class TestRequestSubclass(object): def test_class_implements(self): from repoze.bfg.interfaces import IRequest klass = self._getTargetClass() - iface = self._getInterface() - self.assertTrue(iface.implementedBy(klass)) self.assertTrue(IRequest.implementedBy(klass)) def test_instance_provides(self): from repoze.bfg.interfaces import IRequest inst = self._makeOne({}) - iface = self._getInterface() - self.assertTrue(iface.providedBy(inst)) self.assertTrue(IRequest.providedBy(inst)) - -class Test_Request(TestRequestSubclass, unittest.TestCase): - def _getTargetClass(self): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES[None]['factory'] - - def _getInterface(self): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES[None]['interface'] - -class Test_GETRequest(TestRequestSubclass, unittest.TestCase): - def _getTargetClass(self): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES['GET']['factory'] - - def _getInterface(self): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES['GET']['interface'] - -class Test_POSTRequest(TestRequestSubclass, unittest.TestCase): - def _getTargetClass(self): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES['POST']['factory'] - - def _getInterface(self): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES['POST']['interface'] - -class Test_PUTRequest(TestRequestSubclass, unittest.TestCase): - def _getTargetClass(self): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES['PUT']['factory'] - - def _getInterface(self): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES['PUT']['interface'] - -class Test_DELETERequest(TestRequestSubclass, unittest.TestCase): - def _getTargetClass(self): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES['DELETE']['factory'] - - def _getInterface(self): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES['DELETE']['interface'] - -class Test_HEADRequest(TestRequestSubclass, unittest.TestCase): - def _getTargetClass(self): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES['HEAD']['factory'] - - def _getInterface(self): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES['HEAD']['interface'] +class TestRequest(unittest.TestCase, RequestTestBase): + def _getTargetClass(self): + from repoze.bfg.request import Request + return Request + +class TestRouteRequest(unittest.TestCase, RequestTestBase): + def _getTargetClass(self): + from repoze.bfg.request import create_route_request_factory + return create_route_request_factory('abc') class TestRequestFactory(unittest.TestCase): def setUp(self): @@ -114,187 +65,48 @@ class TestRequestFactory(unittest.TestCase): from repoze.bfg.request import request_factory return request_factory(environ) - def _registerRequestFactories(self, name=''): - from zope.component import getSiteManager - from repoze.bfg.interfaces import IRequestFactories - factories = {} - def factory(environ): - return environ - for name in (None, 'GET', 'POST', 'PUT', 'DELETE', 'HEAD'): - factories[name] = {'factory':factory} - sm = getSiteManager() - sm.registerUtility(factories, IRequestFactories, name=name) - if name: - sm.registerUtility(factories, IRequestFactories, name='') - - def _getRequestFactory(self, name_or_iface=None): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES - return DEFAULT_REQUEST_FACTORIES[name_or_iface]['factory'] - - def _makeRoute(self, name=None): - route = DummyRoute(name) - return route - - def test_no_route_no_request_method(self): + def test_it_no_route(self): from repoze.bfg.interfaces import IRequest + from repoze.bfg.request import Request result = self._callFUT({}) - self.assertEqual(result.__class__, self._getRequestFactory()) - self.failUnless(IRequest.providedBy(result)) - - def test_no_route_unknown(self): - from repoze.bfg.interfaces import IRequest - result = self._callFUT({'REQUEST_METHOD':'UNKNOWN'}) - self.assertEqual(result.__class__, self._getRequestFactory()) + self.assertEqual(result.__class__, Request) self.failUnless(IRequest.providedBy(result)) - def test_no_route_get(self): - from repoze.bfg.interfaces import IGETRequest - result = self._callFUT({'REQUEST_METHOD':'GET'}) - self.assertEqual(result.__class__, self._getRequestFactory('GET')) - self.failUnless(IGETRequest.providedBy(result)) - - def test_no_route_post(self): - from repoze.bfg.interfaces import IPOSTRequest - result = self._callFUT({'REQUEST_METHOD':'POST'}) - self.assertEqual(result.__class__, self._getRequestFactory('POST')) - self.failUnless(IPOSTRequest.providedBy(result)) - - def test_no_route_put(self): - from repoze.bfg.interfaces import IPUTRequest - result = self._callFUT({'REQUEST_METHOD':'PUT'}) - self.assertEqual(result.__class__, self._getRequestFactory('PUT')) - self.failUnless(IPUTRequest.providedBy(result)) - - def test_no_route_delete(self): - from repoze.bfg.interfaces import IDELETERequest - result = self._callFUT({'REQUEST_METHOD':'DELETE'}) - self.assertEqual(result.__class__, self._getRequestFactory('DELETE')) - self.failUnless(IDELETERequest.providedBy(result)) - - def test_no_route_head(self): - from repoze.bfg.interfaces import IHEADRequest - result = self._callFUT({'REQUEST_METHOD':'HEAD'}) - self.assertEqual(result.__class__, self._getRequestFactory('HEAD')) - self.failUnless(IHEADRequest.providedBy(result)) - - def test_route_no_request_method(self): - self._registerRequestFactories() - route = self._makeRoute() - environ = {'bfg.routes.route':route} - result = self._callFUT(environ) - self.assertEqual(result, environ) - - def test_route_unknown(self): - self._registerRequestFactories() - route = self._makeRoute() - environ = {'bfg.routes.route':route, 'REQUEST_METHOD':'UNKNOWN'} - result = self._callFUT(environ) - self.assertEqual(result, environ) - - def test_route_known(self): - self._registerRequestFactories() - route = self._makeRoute() - environ = {'bfg.routes.route':route, 'REQUEST_METHOD':'GET'} - result = self._callFUT(environ) - self.assertEqual(result, environ) - -class TestNamedRequestFactories(unittest.TestCase): + def test_it_with_route_found(self): + from zope.component import getSiteManager + from repoze.bfg.interfaces import IRouteRequest + sm = getSiteManager() + sm.registerUtility(DummyRequest, IRouteRequest, 'routename') + route = DummyRoute('routename') + result = self._callFUT({'bfg.routes.route':route}) + self.assertEqual(result.__class__, DummyRequest) + + def test_it_with_route_notfound(self): + from repoze.bfg.request import Request + route = DummyRoute('routename') + result = self._callFUT({'bfg.routes.route':route}) + self.assertEqual(result.__class__, Request) + +class Test_create_route_request_factory(unittest.TestCase): def _callFUT(self, name): - from repoze.bfg.request import named_request_factories - return named_request_factories(name) - - def test_it_unnamed(self): - factories = self._callFUT(None) - from repoze.bfg.interfaces import IRequest - from repoze.bfg.interfaces import IGETRequest - from repoze.bfg.interfaces import IPOSTRequest - from repoze.bfg.interfaces import IPUTRequest - from repoze.bfg.interfaces import IDELETERequest - from repoze.bfg.interfaces import IHEADRequest - for alias, iface in ( - (None, IRequest), - ('GET', IGETRequest), - ('POST', IPOSTRequest), - ('PUT', IPUTRequest), - ('DELETE', IDELETERequest), - ('HEAD', IHEADRequest), - ): - self.failUnless(alias in factories) - self.failUnless(iface in factories) - self.assertEqual(factories[alias], factories[iface]) - named_iface = factories[alias]['interface'] - named_factory = factories[alias]['factory'] - default_iface = factories[None]['interface'] - self.assertEqual(factories[alias]['interface'], iface) - self.assertEqual(factories[iface]['interface'], iface) - self.assertEqual(factories[alias]['factory'].charset, 'utf-8') - self.failUnless(named_iface.implementedBy(named_factory)) - self.failUnless(iface.implementedBy(named_factory)) - self.failUnless(IRequest.implementedBy(named_factory)) - self.failUnless(default_iface.implementedBy(named_factory)) + from repoze.bfg.request import create_route_request_factory + return create_route_request_factory(name) - def test_it_named(self): - factories = self._callFUT('name') - from repoze.bfg.interfaces import IRequest - from repoze.bfg.interfaces import IGETRequest - from repoze.bfg.interfaces import IPOSTRequest - from repoze.bfg.interfaces import IPUTRequest - from repoze.bfg.interfaces import IDELETERequest - from repoze.bfg.interfaces import IHEADRequest - for alias, iface in ( - (None, IRequest), - ('GET', IGETRequest), - ('POST', IPOSTRequest), - ('PUT', IPUTRequest), - ('DELETE', IDELETERequest), - ('HEAD', IHEADRequest), - ): - self.failUnless(alias in factories) - self.failUnless(iface in factories) - self.assertEqual(factories[alias], factories[iface]) - self.assertEqual(factories[alias]['factory'].charset, 'utf-8') - named_iface = factories[alias]['interface'] - named_factory = factories[alias]['factory'] - default_iface = factories[None]['interface'] - self.failUnless(named_iface.implementedBy(named_factory)) - self.failUnless(iface.implementedBy(named_factory)) - self.failUnless(IRequest.implementedBy(named_factory)) - self.failUnless(default_iface.implementedBy(named_factory)) - -class TestDefaultRequestFactories(unittest.TestCase): def test_it(self): - from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES as factories + from repoze.bfg.interfaces import IRouteRequest from repoze.bfg.interfaces import IRequest - from repoze.bfg.interfaces import IGETRequest - from repoze.bfg.interfaces import IPOSTRequest - from repoze.bfg.interfaces import IPUTRequest - from repoze.bfg.interfaces import IDELETERequest - from repoze.bfg.interfaces import IHEADRequest - for alias, iface in ( - (None, IRequest), - ('GET', IGETRequest), - ('POST', IPOSTRequest), - ('PUT', IPUTRequest), - ('DELETE', IDELETERequest), - ('HEAD', IHEADRequest), - ): - self.failUnless(alias in factories) - self.failUnless(iface in factories) - self.assertEqual(factories[alias], factories[iface]) - named_iface = factories[alias]['interface'] - named_factory = factories[alias]['factory'] - self.failUnless(named_iface.implementedBy(named_factory)) - self.assertEqual(factories[alias]['interface'], iface) - self.assertEqual(factories[iface]['interface'], iface) - self.assertEqual(factories[alias]['factory'].charset, 'utf-8') - + factory = self._callFUT('routename') + self.failUnless(IRouteRequest.implementedBy(factory)) + self.failUnless(IRequest.implementedBy(factory)) class DummyRoute: def __init__(self, name): - self.name=name - + self.name = name class DummyRequest: - pass + def __init__(self, environ=None): + if environ is None: + environ = {} + self.environ = environ class DummyNewRequestEvent: def __init__(self, request): diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py index 566ffe4f1..a96765a01 100644 --- a/repoze/bfg/tests/test_router.py +++ b/repoze/bfg/tests/test_router.py @@ -33,12 +33,6 @@ class RouterTests(unittest.TestCase): settings = Settings(**defaultkw) self.registry.registerUtility(settings, ISettings) - def _registerAuthenticationPolicy(self): - from repoze.bfg.interfaces import IAuthenticationPolicy - policy = DummyAuthenticationPolicy() - self.registry.registerUtility(policy, IAuthenticationPolicy) - return policy - def _registerTraverserFactory(self, context, view_name='', subpath=None, traversed=None, virtual_root=None, virtual_root_path=None, **kw): @@ -75,20 +69,6 @@ class RouterTests(unittest.TestCase): from repoze.bfg.interfaces import IView self.registry.registerAdapter(app, for_, IView, name) - def _registerViewPermission(self, view_name, allow=True): - from zope.interface import Interface - from repoze.bfg.interfaces import IViewPermission - class Checker(object): - def __call__(self, context, request): - self.context = context - self.request = request - return allow - checker = Checker() - self.registry.registerAdapter(checker, (Interface, Interface), - IViewPermission, - view_name) - return checker - def _registerEventListener(self, iface): L = [] def listener(event): @@ -239,31 +219,6 @@ class RouterTests(unittest.TestCase): start_response = DummyStartResponse() self.assertRaises(ValueError, router, environ, start_response) - def test_iforbiddenview_returns_nonresponse(self): - from repoze.bfg.interfaces import IForbiddenView - from zope.interface import Interface - from zope.interface import directlyProvides - class IContext(Interface): - pass - from repoze.bfg.interfaces import IRequest - context = DummyContext() - directlyProvides(context, IContext) - self._registerTraverserFactory(context) - self._registerAuthenticationPolicy() - response = DummyResponse() - view = DummyView(response) - from repoze.bfg.security import ACLDenied - denied = ACLDenied('ace', 'acl', 'permission', ['principals'], context) - environ = self._makeEnviron() - self._registerView(view, '', IContext, IRequest) - checker = self._registerViewPermission('', denied) - def app(context, request): - """ """ - self.registry.registerUtility(app, IForbiddenView) - router = self._makeOne() - start_response = DummyStartResponse() - self.assertRaises(ValueError, router, environ, start_response) - def test_call_view_registered_nonspecific_default_path(self): context = DummyContext() self._registerTraverserFactory(context) @@ -369,72 +324,7 @@ class RouterTests(unittest.TestCase): self.assertEqual(start_response.status, '404 Not Found') self.failUnless('404' in result[0]) - def test_call_view_permission_none(self): - from zope.interface import Interface - from zope.interface import directlyProvides - class IContext(Interface): - pass - from repoze.bfg.interfaces import IRequest - context = DummyContext() - directlyProvides(context, IContext) - self._registerTraverserFactory(context, subpath=['']) - response = DummyResponse() - view = DummyView(response) - environ = self._makeEnviron() - self._registerView(view, '', IContext, IRequest) - router = self._makeOne() - start_response = DummyStartResponse() - result = router(environ, start_response) - self.assertEqual(start_response.status, '200 OK') - - def test_call_view_no_authentication_policy_debug_authorization(self): - logger = self._registerLogger() - from zope.interface import Interface - from zope.interface import directlyProvides - class IContext(Interface): - pass - from repoze.bfg.interfaces import IRequest - context = DummyContext() - directlyProvides(context, IContext) - self._registerTraverserFactory(context, subpath=['']) - response = DummyResponse() - view = DummyView(response) - environ = self._makeEnviron() - self._registerView(view, '', IContext, IRequest) - router = self._makeOne() - router.debug_authorization = True - start_response = DummyStartResponse() - result = router(environ, start_response) - self.assertEqual(start_response.status, '200 OK') - self.assertEqual(len(logger.messages), 1) - self.failUnless('no authentication policy' in logger.messages[0]) - - def test_call_view_no_permission_registered_debug_authorization(self): - self._registerAuthenticationPolicy() - logger = self._registerLogger() - from zope.interface import Interface - from zope.interface import directlyProvides - class IContext(Interface): - pass - from repoze.bfg.interfaces import IRequest - context = DummyContext() - directlyProvides(context, IContext) - self._registerTraverserFactory(context, subpath=['']) - response = DummyResponse() - view = DummyView(response) - environ = self._makeEnviron() - self._registerView(view, '', IContext, IRequest) - router = self._makeOne() - router.debug_authorization = True - start_response = DummyStartResponse() - result = router(environ, start_response) - self.assertEqual(start_response.status, '200 OK') - self.assertEqual(len(logger.messages), 1) - self.failUnless('no permission registered' in logger.messages[0]) - - def test_call_view_no_permission_registered_no_debug(self): - self._registerAuthenticationPolicy() - logger = self._registerLogger() + def test_call_view_raises_unauthorized(self): from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): @@ -444,63 +334,16 @@ class RouterTests(unittest.TestCase): directlyProvides(context, IContext) self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() - view = DummyView(response) + view = DummyView(response, raise_unauthorized=True) environ = self._makeEnviron() self._registerView(view, '', IContext, IRequest) router = self._makeOne() - router.debug_authorization = False start_response = DummyStartResponse() - result = router(environ, start_response) - self.assertEqual(start_response.status, '200 OK') - self.assertEqual(len(logger.messages), 0) - - def test_call_view_permission_succeeds(self): - from zope.interface import Interface - from zope.interface import directlyProvides - class IContext(Interface): - pass - from repoze.bfg.interfaces import IRequest - context = DummyContext() - directlyProvides(context, IContext) - self._registerTraverserFactory(context, subpath=['']) - self._registerAuthenticationPolicy() - response = DummyResponse() - view = DummyView(response) - environ = self._makeEnviron() - self._registerView(view, '', IContext, IRequest) - checker = self._registerViewPermission('', True) - router = self._makeOne() - start_response = DummyStartResponse() - result = router(environ, start_response) - self.assertEqual(start_response.status, '200 OK') - self.assertEqual(checker.context, context) - - def test_call_view_permission_fails_nosettings(self): - from zope.interface import Interface - from zope.interface import directlyProvides - class IContext(Interface): - pass - from repoze.bfg.interfaces import IRequest - context = DummyContext() - directlyProvides(context, IContext) - self._registerTraverserFactory(context, subpath=['']) - self._registerAuthenticationPolicy() - response = DummyResponse() - view = DummyView(response) - from repoze.bfg.security import ACLDenied - denied = ACLDenied('ace', 'acl', 'permission', ['principals'], context) - environ = self._makeEnviron() - self._registerView(view, '', IContext, IRequest) - checker = self._registerViewPermission('', denied) - router = self._makeOne() - start_response = DummyStartResponse() - result = router(environ, start_response) + response = router(environ, start_response) self.assertEqual(start_response.status, '401 Unauthorized') - message = environ['repoze.bfg.message'] - self.assertEqual(message, 'Unauthorized: failed security policy check') - self.assertEqual(checker.context, context) + self.assertEqual(environ['repoze.bfg.message'], 'unauthorized') - def test_call_view_permission_fails_no_debug_auth(self): + def test_call_view_raises_notfound(self): from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): @@ -509,62 +352,15 @@ class RouterTests(unittest.TestCase): context = DummyContext() directlyProvides(context, IContext) self._registerTraverserFactory(context, subpath=['']) - self._registerAuthenticationPolicy() response = DummyResponse() - view = DummyView(response) - from repoze.bfg.security import ACLDenied - denied = ACLDenied('ace', 'acl', 'permission', ['principals'], context) + view = DummyView(response, raise_notfound=True) environ = self._makeEnviron() self._registerView(view, '', IContext, IRequest) - checker = self._registerViewPermission('', denied) - self._registerSettings(debug_authorization=False) router = self._makeOne() start_response = DummyStartResponse() - result = router(environ, start_response) - self.assertEqual(start_response.status, '401 Unauthorized') - message = environ['repoze.bfg.message'] - self.failUnless('failed security policy check' in message) - self.assertEqual(checker.context, context) - - def test_call_view_permission_fails_with_debug_auth(self): - from zope.interface import Interface - from zope.interface import directlyProvides - class IContext(Interface): - pass - from repoze.bfg.interfaces import IRequest - context = DummyContext() - directlyProvides(context, IContext) - self._registerAuthenticationPolicy() - self._registerTraverserFactory(context, subpath=['']) - response = DummyResponse() - view = DummyView(response) - from repoze.bfg.security import ACLDenied - environ = self._makeEnviron() - self._registerView(view, '', IContext, IRequest) - allowed = ACLDenied('ace', 'acl', 'permission', ['principals'], context) - checker = self._registerViewPermission('', allowed) - self._registerSettings(debug_authorization=True) - logger = self._registerLogger() - router = self._makeOne() - start_response = DummyStartResponse() - result = router(environ, start_response) - self.assertEqual(start_response.status, '401 Unauthorized') - message = environ['repoze.bfg.message'] - self.failUnless( - "ACLDenied permission 'permission' via ACE 'ace' in ACL 'acl' " - "on context" in message) - self.failUnless("for principals ['principals']" in message) - self.assertEqual(checker.context, context) - self.assertEqual(len(logger.messages), 1) - logged = logger.messages[0] - self.failUnless( - "debug_authorization of url http://localhost:8080/ (view name " - "'' against context" in logged) - self.failUnless( - "ACLDenied permission 'permission' via ACE 'ace' in ACL 'acl' on " - "context" in logged) - self.failUnless( - "for principals ['principals']" in logged) + response = router(environ, start_response) + self.assertEqual(start_response.status, '404 Not Found') + self.assertEqual(environ['repoze.bfg.message'], 'notfound') def test_call_eventsends(self): context = DummyContext() @@ -601,65 +397,6 @@ class RouterTests(unittest.TestCase): self.assertEqual(len(router.threadlocal_manager.pushed), 1) self.assertEqual(len(router.threadlocal_manager.popped), 1) - def test_call_post_method(self): - from repoze.bfg.interfaces import INewRequest - from repoze.bfg.interfaces import IPOSTRequest - from repoze.bfg.interfaces import IPUTRequest - from repoze.bfg.interfaces import IRequest - context = DummyContext() - self._registerTraverserFactory(context) - response = DummyResponse() - response.app_iter = ['Hello world'] - view = DummyView(response) - environ = self._makeEnviron(REQUEST_METHOD='POST') - self._registerView(view, '', None, None) - router = self._makeOne() - start_response = DummyStartResponse() - request_events = self._registerEventListener(INewRequest) - result = router(environ, start_response) - request = request_events[0].request - self.failUnless(IPOSTRequest.providedBy(request)) - self.failIf(IPUTRequest.providedBy(request)) - self.failUnless(IRequest.providedBy(request)) - - def test_call_put_method(self): - from repoze.bfg.interfaces import INewRequest - from repoze.bfg.interfaces import IPUTRequest - from repoze.bfg.interfaces import IPOSTRequest - from repoze.bfg.interfaces import IRequest - context = DummyContext() - self._registerTraverserFactory(context) - response = DummyResponse() - response.app_iter = ['Hello world'] - view = DummyView(response) - environ = self._makeEnviron(REQUEST_METHOD='PUT') - self._registerView(view, '', None, None) - router = self._makeOne() - start_response = DummyStartResponse() - request_events = self._registerEventListener(INewRequest) - result = router(environ, start_response) - request = request_events[0].request - self.failUnless(IPUTRequest.providedBy(request)) - self.failIf(IPOSTRequest.providedBy(request)) - self.failUnless(IRequest.providedBy(request)) - - def test_call_unknown_method(self): - from repoze.bfg.interfaces import INewRequest - from repoze.bfg.interfaces import IRequest - context = DummyContext() - self._registerTraverserFactory(context) - response = DummyResponse() - response.app_iter = ['Hello world'] - view = DummyView(response) - environ = self._makeEnviron(REQUEST_METHOD='UNKNOWN') - self._registerView(view, '', None, None) - router = self._makeOne() - start_response = DummyStartResponse() - request_events = self._registerEventListener(INewRequest) - result = router(environ, start_response) - request = request_events[0].request - self.failUnless(IRequest.providedBy(request)) - class MakeAppTests(unittest.TestCase): def setUp(self): cleanUp() @@ -917,10 +654,19 @@ class DummyContext: pass class DummyView: - def __init__(self, response): + def __init__(self, response, raise_unauthorized=False, + raise_notfound=False): self.response = response + self.raise_unauthorized = raise_unauthorized + self.raise_notfound = raise_notfound def __call__(self, context, request): + if self.raise_unauthorized: + from repoze.bfg.security import Unauthorized + raise Unauthorized('unauthorized') + if self.raise_notfound: + from repoze.bfg.view import NotFound + raise NotFound('notfound') return self.response class DummyRootFactory: diff --git a/repoze/bfg/tests/test_security.py b/repoze/bfg/tests/test_security.py index bb1a54e04..e06386fdb 100644 --- a/repoze/bfg/tests/test_security.py +++ b/repoze/bfg/tests/test_security.py @@ -27,29 +27,6 @@ class TestAllPermissionsList(unittest.TestCase): from repoze.bfg.security import ALL_PERMISSIONS self.assertEqual(ALL_PERMISSIONS.__class__, self._getTargetClass()) -class TestViewPermissionFactory(unittest.TestCase): - def setUp(self): - cleanUp() - - def tearDown(self): - cleanUp() - - def _getTargetClass(self): - from repoze.bfg.security import ViewPermissionFactory - return ViewPermissionFactory - - def _makeOne(self, *arg, **kw): - klass = self._getTargetClass() - return klass(*arg, **kw) - - def test_call(self): - context = DummyContext() - request = DummyRequest({}) - factory = self._makeOne('repoze.view') - self.assertEqual(factory.permission_name, 'repoze.view') - result = factory(context, request) - self.assertEqual(result, True) - class TestAllowed(unittest.TestCase): def _getTargetClass(self): from repoze.bfg.security import Allowed @@ -137,19 +114,19 @@ class TestViewExecutionPermitted(unittest.TestCase): from repoze.bfg.security import view_execution_permitted return view_execution_permitted(*arg, **kw) - def _registerViewPermission(self, view_name, allow=True): + def _registerSecuredView(self, view_name, allow=True): import zope.component from zope.interface import Interface - from repoze.bfg.interfaces import IViewPermission + from repoze.bfg.interfaces import ISecuredView class Checker(object): - def __call__(self, context, request): + def __permitted__(self, context, request): self.context = context self.request = request return allow checker = Checker() gsm = zope.component.getGlobalSiteManager() gsm.registerAdapter(checker, (Interface, Interface), - IViewPermission, + ISecuredView, view_name) return checker @@ -175,7 +152,7 @@ class TestViewExecutionPermitted(unittest.TestCase): pass context = DummyContext() directlyProvides(context, IContext) - checker = self._registerViewPermission('', True) + checker = self._registerSecuredView('', True) request = DummyRequest({}) directlyProvides(request, IRequest) result = self._callFUT(context, request, '') diff --git a/repoze/bfg/tests/test_testing.py b/repoze/bfg/tests/test_testing.py index 4ad3c83f6..e81a90219 100644 --- a/repoze/bfg/tests/test_testing.py +++ b/repoze/bfg/tests/test_testing.py @@ -4,9 +4,13 @@ import unittest class TestTestingFunctions(unittest.TestCase): def setUp(self): cleanUp() + from zope.deprecation import __show__ + __show__.off() def tearDown(self): cleanUp() + from zope.deprecation import __show__ + __show__.on() def test_registerDummySecurityPolicy(self): from repoze.bfg import testing @@ -143,34 +147,78 @@ class TestTestingFunctions(unittest.TestCase): response = render_view_to_response(None, None, 'moo.html') self.assertEqual(response.body, '123') - def test_registerViewPermission_defaults(self): + def test_registerView_with_permission_denying(self): + from repoze.bfg import testing + from repoze.bfg.security import Unauthorized + def view(context, request): + """ """ + view = testing.registerView('moo.html', view=view, permission='bar') + testing.registerDummySecurityPolicy(permissive=False) + import types + self.failUnless(isinstance(view, types.FunctionType)) + from repoze.bfg.view import render_view_to_response + self.assertRaises(Unauthorized, render_view_to_response, + None, None, 'moo.html') + + def test_registerView_with_permission_denying2(self): + from repoze.bfg import testing from repoze.bfg.security import view_execution_permitted + def view(context, request): + """ """ + view = testing.registerView('moo.html', view=view, permission='bar') + testing.registerDummySecurityPolicy(permissive=False) + import types + self.failUnless(isinstance(view, types.FunctionType)) + result = view_execution_permitted(None, None, 'moo.html') + self.assertEqual(result, False) + + def test_registerView_with_permission_allowing(self): + from repoze.bfg import testing + def view(context, request): + from webob import Response + return Response('123') + view = testing.registerView('moo.html', view=view, permission='bar') + testing.registerDummySecurityPolicy(permissive=True) + import types + self.failUnless(isinstance(view, types.FunctionType)) + from repoze.bfg.view import render_view_to_response + result = render_view_to_response(None, None, 'moo.html') + self.assertEqual(result.app_iter, ['123']) + + def test_registerViewPermission_defaults(self): + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IViewPermission from repoze.bfg import testing view = testing.registerViewPermission('moo.html') - testing.registerDummySecurityPolicy() - result = view_execution_permitted(None, None, 'moo.html') - self.failUnless(result) - self.assertEqual(result.msg, 'message') + sm = getSiteManager() + result = sm.getMultiAdapter( + (Interface, Interface), IViewPermission, 'moo.html') + self.assertEqual(result, True) def test_registerViewPermission_denying(self): - from repoze.bfg.security import view_execution_permitted + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IViewPermission from repoze.bfg import testing view = testing.registerViewPermission('moo.html', result=False) - testing.registerDummySecurityPolicy() - result = view_execution_permitted(None, None, 'moo.html') - self.failIf(result) - self.assertEqual(result.msg, 'message') + sm = getSiteManager() + result = sm.getMultiAdapter( + (Interface, Interface), IViewPermission, 'moo.html') + self.assertEqual(result, False) def test_registerViewPermission_custom(self): - from repoze.bfg.security import view_execution_permitted + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IViewPermission def viewperm(context, request): return True from repoze.bfg import testing - view = testing.registerViewPermission('moo.html', - viewpermission=viewperm) - testing.registerDummySecurityPolicy() - result = view_execution_permitted(None, None, 'moo.html') - self.failUnless(result is True) + testing.registerViewPermission('moo.html', viewpermission=viewperm) + sm = getSiteManager() + result = sm.getMultiAdapter( + (Interface, Interface), IViewPermission, 'moo.html') + self.assertEqual(result, True) def test_registerAdapter(self): from zope.interface import implements diff --git a/repoze/bfg/tests/test_view.py b/repoze/bfg/tests/test_view.py index 3c7d6f853..f496ebb36 100644 --- a/repoze/bfg/tests/test_view.py +++ b/repoze/bfg/tests/test_view.py @@ -15,28 +15,6 @@ class BaseTest(object): from repoze.bfg.interfaces import IView gsm.registerAdapter(app, for_, IView, name) - def _registerViewPermission(self, view_name, allow=True): - import zope.component - from zope.interface import Interface - from repoze.bfg.interfaces import IViewPermission - class Checker(object): - def __call__(self, context, request): - self.context = context - self.request = request - return allow - checker = Checker() - gsm = zope.component.getGlobalSiteManager() - gsm.registerAdapter(checker, (Interface, Interface), - IViewPermission, - view_name) - return checker - - def _registerSecurityPolicy(self, secpol): - import zope.component - gsm = zope.component.getGlobalSiteManager() - from repoze.bfg.interfaces import ISecurityPolicy - gsm.registerUtility(secpol, ISecurityPolicy) - def _makeEnviron(self, **extras): environ = { 'wsgi.url_scheme':'http', @@ -61,7 +39,7 @@ class RenderViewToResponseTests(BaseTest, unittest.TestCase): result = self._callFUT(context, request, name='notregistered') self.assertEqual(result, None) - def test_call_view_registered_secure_permission_disallows(self): + def test_call_view_registered_secure(self): context = DummyContext() from zope.interface import Interface from zope.interface import directlyProvides @@ -70,20 +48,18 @@ class RenderViewToResponseTests(BaseTest, unittest.TestCase): pass directlyProvides(context, IContext) response = DummyResponse() - secpol = DummySecurityPolicy() view = make_view(response) self._registerView(view, 'registered', IContext, IRequest) - self._registerSecurityPolicy(secpol) - self._registerViewPermission('registered', False) environ = self._makeEnviron() from webob import Request request = Request(environ) directlyProvides(request, IRequest) - from repoze.bfg.security import Unauthorized - self.assertRaises(Unauthorized, self._callFUT, context, request, - name='registered', secure=True) + response = self._callFUT(context, request, name='registered', + secure=True) + self.assertEqual(response.status, '200 OK') + - def test_call_view_registered_secure_permission_allows(self): + def test_call_view_registered_insecure_no_call_permissive(self): context = DummyContext() from zope.interface import Interface from zope.interface import directlyProvides @@ -92,20 +68,17 @@ class RenderViewToResponseTests(BaseTest, unittest.TestCase): pass directlyProvides(context, IContext) response = DummyResponse() - secpol = DummySecurityPolicy() view = make_view(response) self._registerView(view, 'registered', IContext, IRequest) - self._registerSecurityPolicy(secpol) - self._registerViewPermission('registered', True) environ = self._makeEnviron() from webob import Request request = Request(environ) directlyProvides(request, IRequest) response = self._callFUT(context, request, name='registered', - secure=True) + secure=False) self.assertEqual(response.status, '200 OK') - def test_call_view_registered_insecure_permission_disallows(self): + def test_call_view_registered_insecure_with_call_permissive(self): context = DummyContext() from zope.interface import Interface from zope.interface import directlyProvides @@ -114,11 +87,11 @@ class RenderViewToResponseTests(BaseTest, unittest.TestCase): pass directlyProvides(context, IContext) response = DummyResponse() - secpol = DummySecurityPolicy() view = make_view(response) + def anotherview(context, request): + return DummyResponse('anotherview') + view.__call_permissive__ = anotherview self._registerView(view, 'registered', IContext, IRequest) - self._registerSecurityPolicy(secpol) - self._registerViewPermission('registered', False) environ = self._makeEnviron() from webob import Request request = Request(environ) @@ -126,6 +99,7 @@ class RenderViewToResponseTests(BaseTest, unittest.TestCase): response = self._callFUT(context, request, name='registered', secure=False) self.assertEqual(response.status, '200 OK') + self.assertEqual(response.app_iter, ['anotherview']) class RenderViewToIterableTests(BaseTest, unittest.TestCase): def _callFUT(self, *arg, **kw): @@ -140,7 +114,7 @@ class RenderViewToIterableTests(BaseTest, unittest.TestCase): result = self._callFUT(context, request, name='notregistered') self.assertEqual(result, None) - def test_call_view_registered_secure_permission_disallows(self): + def test_call_view_registered_secure(self): context = DummyContext() from zope.interface import Interface from zope.interface import directlyProvides @@ -149,20 +123,17 @@ class RenderViewToIterableTests(BaseTest, unittest.TestCase): pass directlyProvides(context, IContext) response = DummyResponse() - secpol = DummySecurityPolicy() view = make_view(response) self._registerView(view, 'registered', IContext, IRequest) - self._registerSecurityPolicy(secpol) - self._registerViewPermission('registered', False) environ = self._makeEnviron() from webob import Request request = Request(environ) directlyProvides(request, IRequest) - from repoze.bfg.security import Unauthorized - self.assertRaises(Unauthorized, self._callFUT, context, request, - name='registered', secure=True) + iterable = self._callFUT(context, request, name='registered', + secure=True) + self.assertEqual(iterable, ()) - def test_call_view_registered_secure_permission_allows(self): + def test_call_view_registered_insecure_no_call_permissive(self): context = DummyContext() from zope.interface import Interface from zope.interface import directlyProvides @@ -171,20 +142,17 @@ class RenderViewToIterableTests(BaseTest, unittest.TestCase): pass directlyProvides(context, IContext) response = DummyResponse() - secpol = DummySecurityPolicy() view = make_view(response) self._registerView(view, 'registered', IContext, IRequest) - self._registerSecurityPolicy(secpol) - self._registerViewPermission('registered', True) environ = self._makeEnviron() from webob import Request request = Request(environ) directlyProvides(request, IRequest) iterable = self._callFUT(context, request, name='registered', - secure=True) + secure=False) self.assertEqual(iterable, ()) - def test_call_view_registered_insecure_permission_disallows(self): + def test_call_view_registered_insecure_with_call_permissive(self): context = DummyContext() from zope.interface import Interface from zope.interface import directlyProvides @@ -193,18 +161,18 @@ class RenderViewToIterableTests(BaseTest, unittest.TestCase): pass directlyProvides(context, IContext) response = DummyResponse() - secpol = DummySecurityPolicy() view = make_view(response) + def anotherview(context, request): + return DummyResponse('anotherview') + view.__call_permissive__ = anotherview self._registerView(view, 'registered', IContext, IRequest) - self._registerSecurityPolicy(secpol) - self._registerViewPermission('registered', False) environ = self._makeEnviron() from webob import Request request = Request(environ) directlyProvides(request, IRequest) iterable = self._callFUT(context, request, name='registered', secure=False) - self.assertEqual(iterable, ()) + self.assertEqual(iterable, ['anotherview']) class RenderViewTests(unittest.TestCase, BaseTest): def _callFUT(self, *arg, **kw): @@ -219,7 +187,7 @@ class RenderViewTests(unittest.TestCase, BaseTest): result = self._callFUT(context, request, name='notregistered') self.assertEqual(result, None) - def test_call_view_registered_secure_permission_disallows(self): + def test_call_view_registered_secure(self): context = DummyContext() from zope.interface import Interface from zope.interface import directlyProvides @@ -228,20 +196,16 @@ class RenderViewTests(unittest.TestCase, BaseTest): pass directlyProvides(context, IContext) response = DummyResponse() - secpol = DummySecurityPolicy() view = make_view(response) self._registerView(view, 'registered', IContext, IRequest) - self._registerSecurityPolicy(secpol) - self._registerViewPermission('registered', False) environ = self._makeEnviron() from webob import Request request = Request(environ) directlyProvides(request, IRequest) - from repoze.bfg.security import Unauthorized - self.assertRaises(Unauthorized, self._callFUT, context, request, - name='registered', secure=True) + s = self._callFUT(context, request, name='registered', secure=True) + self.assertEqual(s, '') - def test_call_view_registered_secure_permission_allows(self): + def test_call_view_registered_insecure_no_call_permissive(self): context = DummyContext() from zope.interface import Interface from zope.interface import directlyProvides @@ -250,19 +214,16 @@ class RenderViewTests(unittest.TestCase, BaseTest): pass directlyProvides(context, IContext) response = DummyResponse() - secpol = DummySecurityPolicy() view = make_view(response) self._registerView(view, 'registered', IContext, IRequest) - self._registerSecurityPolicy(secpol) - self._registerViewPermission('registered', True) environ = self._makeEnviron() from webob import Request request = Request(environ) directlyProvides(request, IRequest) - s = self._callFUT(context, request, name='registered', secure=True) + s = self._callFUT(context, request, name='registered', secure=False) self.assertEqual(s, '') - def test_call_view_registered_insecure_permission_disallows(self): + def test_call_view_registered_insecure_with_call_permissive(self): context = DummyContext() from zope.interface import Interface from zope.interface import directlyProvides @@ -271,17 +232,17 @@ class RenderViewTests(unittest.TestCase, BaseTest): pass directlyProvides(context, IContext) response = DummyResponse() - secpol = DummySecurityPolicy() view = make_view(response) + def anotherview(context, request): + return DummyResponse('anotherview') + view.__call_permissive__ = anotherview self._registerView(view, 'registered', IContext, IRequest) - self._registerSecurityPolicy(secpol) - self._registerViewPermission('registered', False) environ = self._makeEnviron() from webob import Request request = Request(environ) directlyProvides(request, IRequest) s = self._callFUT(context, request, name='registered', secure=False) - self.assertEqual(s, '') + self.assertEqual(s, 'anotherview') class TestIsResponse(unittest.TestCase): def _callFUT(self, *arg, **kw): @@ -506,6 +467,461 @@ class TestDefaultNotFoundView(unittest.TestCase): self.assertEqual(response.status, '404 Not Found') self.failUnless('<code>abc&123</code>' in response.body) +class TestMultiView(unittest.TestCase): + def _getTargetClass(self): + from repoze.bfg.view import MultiView + return MultiView + + def _makeOne(self, name='name'): + return self._getTargetClass()(name) + + def test_class_implements_ISecuredView(self): + from zope.interface.verify import verifyClass + from repoze.bfg.interfaces import ISecuredView + verifyClass(ISecuredView, self._getTargetClass()) + + def test_instance_implements_ISecuredView(self): + from zope.interface.verify import verifyObject + from repoze.bfg.interfaces import ISecuredView + verifyObject(ISecuredView, self._makeOne()) + + def test_add(self): + mv = self._makeOne() + mv.add('view', 100) + self.assertEqual(mv.views, [(100, 'view')]) + mv.add('view2', 99) + self.assertEqual(mv.views, [(99, 'view2'), (100, 'view')]) + + def test_match_not_found(self): + from repoze.bfg.view import NotFound + mv = self._makeOne() + context = DummyContext() + request = DummyRequest() + self.assertRaises(NotFound, mv.match, context, request) + + def test_match_predicate_fails(self): + from repoze.bfg.view import NotFound + mv = self._makeOne() + def view(context, request): + """ """ + view.__predicated__ = lambda *arg: False + mv.views = [(100, view)] + context = DummyContext() + request = DummyRequest() + self.assertRaises(NotFound, mv.match, context, request) + + def test_match_predicate_succeeds(self): + mv = self._makeOne() + def view(context, request): + """ """ + view.__predicated__ = lambda *arg: True + mv.views = [(100, view)] + context = DummyContext() + request = DummyRequest() + result = mv.match(context, request) + self.assertEqual(result, view) + + def test_permitted_no_views(self): + from repoze.bfg.view import NotFound + mv = self._makeOne() + context = DummyContext() + request = DummyRequest() + self.assertRaises(NotFound, mv.__permitted__, context, request) + + def test_permitted_no_match_with__permitted__(self): + mv = self._makeOne() + def view(context, request): + """ """ + mv.views = [(100, view)] + context = DummyContext() + request = DummyRequest() + self.assertEqual(mv.__permitted__(None, None), True) + + def test_permitted(self): + from zope.component import getSiteManager + mv = self._makeOne() + def view(context, request): + """ """ + def permitted(context, request): + return False + view.__permitted__ = permitted + mv.views = [(100, view)] + context = DummyContext() + request = DummyRequest() + sm = getSiteManager() + result = mv.__permitted__(context, request) + self.assertEqual(result, False) + + def test__call__not_found(self): + from repoze.bfg.view import NotFound + mv = self._makeOne() + context = DummyContext() + request = DummyRequest() + self.assertRaises(NotFound, mv, context, request) + + def test___call__intermediate_not_found(self): + from repoze.bfg.view import NotFound + mv = self._makeOne() + context = DummyContext() + request = DummyRequest() + request.view_name = '' + expected_response = DummyResponse() + def view1(context, request): + raise NotFound + def view2(context, request): + return expected_response + mv.views = [(100, view1), (99, view2)] + response = mv(context, request) + self.assertEqual(response, expected_response) + + def test___call__(self): + mv = self._makeOne() + context = DummyContext() + request = DummyRequest() + request.view_name = '' + expected_response = DummyResponse() + def view(context, request): + return expected_response + mv.views = [(100, view)] + response = mv(context, request) + self.assertEqual(response, expected_response) + + def test__call_permissive__not_found(self): + from repoze.bfg.view import NotFound + mv = self._makeOne() + context = DummyContext() + request = DummyRequest() + self.assertRaises(NotFound, mv, context, request) + + def test___call_permissive_has_call_permissive(self): + mv = self._makeOne() + context = DummyContext() + request = DummyRequest() + request.view_name = '' + expected_response = DummyResponse() + def view(context, request): + """ """ + def permissive(context, request): + return expected_response + view.__call_permissive__ = permissive + mv.views = [(100, view)] + response = mv.__call_permissive__(context, request) + self.assertEqual(response, expected_response) + + def test___call_permissive_has_no_call_permissive(self): + mv = self._makeOne() + context = DummyContext() + request = DummyRequest() + request.view_name = '' + expected_response = DummyResponse() + def view(context, request): + return expected_response + mv.views = [(100, view)] + response = mv.__call_permissive__(context, request) + self.assertEqual(response, expected_response) + +class TestMapView(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _callFUT(self, view, *arg, **kw): + from repoze.bfg.view import map_view + return map_view(view, *arg, **kw) + + def test_view_as_function_context_and_request(self): + def view(context, request): + return 'OK' + result = self._callFUT(view) + self.failUnless(result is view) + self.assertEqual(view(None, None), 'OK') + + def test_view_as_function_requestonly(self): + def view(request): + return 'OK' + result = self._callFUT(view) + self.failIf(result is view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.assertEqual(result(None, None), 'OK') + + def test_view_as_newstyle_class_context_and_request(self): + class view(object): + def __init__(self, context, request): + pass + def __call__(self): + return 'OK' + result = self._callFUT(view) + self.failIf(result is view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.assertEqual(result(None, None), 'OK') + + def test_view_as_newstyle_class_requestonly(self): + class view(object): + def __init__(self, context, request): + pass + def __call__(self): + return 'OK' + result = self._callFUT(view) + self.failIf(result is view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.assertEqual(result(None, None), 'OK') + + def test_view_as_oldstyle_class_context_and_request(self): + class view: + def __init__(self, context, request): + pass + def __call__(self): + return 'OK' + result = self._callFUT(view) + self.failIf(result is view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.assertEqual(result(None, None), 'OK') + + def test_view_as_oldstyle_class_requestonly(self): + class view: + def __init__(self, context, request): + pass + def __call__(self): + return 'OK' + result = self._callFUT(view) + self.failIf(result is view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.assertEqual(result(None, None), 'OK') + + def test_view_as_instance_context_and_request(self): + class View: + def __call__(self, context, request): + return 'OK' + view = View() + result = self._callFUT(view) + self.failUnless(result is view) + self.assertEqual(result(None, None), 'OK') + + def test_view_as_instance_requestonly(self): + class View: + def __call__(self, request): + return 'OK' + view = View() + result = self._callFUT(view) + self.failIf(result is view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.failUnless('instance' in result.__name__) + self.assertEqual(result(None, None), 'OK') + +class TestRequestOnly(unittest.TestCase): + def _callFUT(self, arg): + from repoze.bfg.view import requestonly + return requestonly(arg) + + def test_newstyle_class_no_init(self): + class foo(object): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_newstyle_class_init_toomanyargs(self): + class foo(object): + def __init__(self, context, request): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_newstyle_class_init_onearg_named_request(self): + class foo(object): + def __init__(self, request): + """ """ + self.assertTrue(self._callFUT(foo)) + + def test_newstyle_class_init_onearg_named_somethingelse(self): + class foo(object): + def __init__(self, req): + """ """ + self.assertTrue(self._callFUT(foo)) + + def test_newstyle_class_init_defaultargs_firstname_not_request(self): + class foo(object): + def __init__(self, context, request=None): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_newstyle_class_init_defaultargs_firstname_request(self): + class foo(object): + def __init__(self, request, foo=1, bar=2): + """ """ + self.assertTrue(self._callFUT(foo)) + + def test_newstyle_class_init_noargs(self): + class foo(object): + def __init__(): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_oldstyle_class_no_init(self): + class foo: + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_oldstyle_class_init_toomanyargs(self): + class foo: + def __init__(self, context, request): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_oldstyle_class_init_onearg_named_request(self): + class foo: + def __init__(self, request): + """ """ + self.assertTrue(self._callFUT(foo)) + + def test_oldstyle_class_init_onearg_named_somethingelse(self): + class foo: + def __init__(self, req): + """ """ + self.assertTrue(self._callFUT(foo)) + + def test_oldstyle_class_init_defaultargs_firstname_not_request(self): + class foo: + def __init__(self, context, request=None): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_oldstyle_class_init_defaultargs_firstname_request(self): + class foo: + def __init__(self, request, foo=1, bar=2): + """ """ + self.assertTrue(self._callFUT(foo), True) + + def test_oldstyle_class_init_noargs(self): + class foo: + def __init__(): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_function_toomanyargs(self): + def foo(context, request): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_function_onearg_named_request(self): + def foo(request): + """ """ + self.assertTrue(self._callFUT(foo)) + + def test_function_onearg_named_somethingelse(self): + def foo(req): + """ """ + self.assertTrue(self._callFUT(foo)) + + def test_function_defaultargs_firstname_not_request(self): + def foo(context, request=None): + """ """ + self.assertFalse(self._callFUT(foo)) + + def test_function_defaultargs_firstname_request(self): + def foo(request, foo=1, bar=2): + """ """ + self.assertTrue(self._callFUT(foo), True) + + def test_instance_toomanyargs(self): + class Foo: + def __call__(self, context, request): + """ """ + foo = Foo() + self.assertFalse(self._callFUT(foo)) + + def test_instance_defaultargs_onearg_named_request(self): + class Foo: + def __call__(self, request): + """ """ + foo = Foo() + self.assertTrue(self._callFUT(foo)) + + def test_instance_defaultargs_onearg_named_somethingelse(self): + class Foo: + def __call__(self, req): + """ """ + foo = Foo() + self.assertTrue(self._callFUT(foo)) + + def test_instance_defaultargs_firstname_not_request(self): + class Foo: + def __call__(self, context, request=None): + """ """ + foo = Foo() + self.assertFalse(self._callFUT(foo)) + + def test_instance_defaultargs_firstname_request(self): + class Foo: + def __call__(self, request, foo=1, bar=2): + """ """ + foo = Foo() + self.assertTrue(self._callFUT(foo), True) + + def test_instance_nocall(self): + class Foo: pass + foo = Foo() + self.assertFalse(self._callFUT(foo)) + +class TestDecorateView(unittest.TestCase): + def _callFUT(self, wrapped, original): + from repoze.bfg.view import decorate_view + return decorate_view(wrapped, original) + + def test_it_same(self): + def view(context, request): + """ """ + result = self._callFUT(view, view) + self.assertEqual(result, False) + + def test_it_different(self): + class DummyView1: + """ 1 """ + __name__ = '1' + __module__ = '1' + def __call__(self, context, request): + """ """ + def __call_permissive__(self, context, reuqest): + """ """ + def __predicated__(self, context, reuqest): + """ """ + def __permitted__(self, context, request): + """ """ + class DummyView2: + """ 2 """ + __name__ = '2' + __module__ = '2' + def __call__(self, context, request): + """ """ + def __call_permissive__(self, context, reuqest): + """ """ + def __predicated__(self, context, reuqest): + """ """ + def __permitted__(self, context, request): + """ """ + view1 = DummyView1() + view2 = DummyView2() + result = self._callFUT(view1, view2) + self.assertEqual(result, True) + self.failUnless(view1.__doc__ is view2.__doc__) + self.failUnless(view1.__module__ is view2.__module__) + self.failUnless(view1.__name__ is view2.__name__) + self.failUnless(view1.__call_permissive__.im_func is + view2.__call_permissive__.im_func) + self.failUnless(view1.__permitted__.im_func is + view2.__permitted__.im_func) + self.failUnless(view1.__predicated__.im_func is + view2.__predicated__.im_func) class DummyContext: pass @@ -531,8 +947,9 @@ def make_view(response): class DummyResponse: status = '200 OK' headerlist = () - app_iter = () - -class DummySecurityPolicy: - pass - + def __init__(self, body=None): + if body is None: + self.app_iter = () + else: + self.app_iter = [body] + diff --git a/repoze/bfg/tests/test_zcml.py b/repoze/bfg/tests/test_zcml.py index 49baa3a34..fdf8fa9e3 100644 --- a/repoze/bfg/tests/test_zcml.py +++ b/repoze/bfg/tests/test_zcml.py @@ -20,185 +20,144 @@ class TestViewDirective(unittest.TestCase): 'repoze.view', None) def test_view_as_function(self): + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IViewPermission context = DummyContext() - class IFoo: + class IFoo(Interface): pass view = lambda *arg: None self._callFUT(context, 'repoze.view', IFoo, view=view) actions = context.actions - from repoze.bfg.interfaces import IRequest - from repoze.bfg.interfaces import IView - from repoze.bfg.interfaces import IViewPermission - from repoze.bfg.security import ViewPermissionFactory - from repoze.bfg.zcml import handler - self.assertEqual(len(actions), 2) + self.assertEqual(len(actions), 1) - permission = actions[0] - permission_discriminator = ('permission', IFoo, '', IRequest, - IViewPermission) - self.assertEqual(permission['discriminator'], permission_discriminator) - self.assertEqual(permission['callable'], handler) - self.assertEqual(permission['args'][0], 'registerAdapter') - self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory)) - self.assertEqual(permission['args'][1].permission_name, 'repoze.view') - self.assertEqual(permission['args'][2], (IFoo, IRequest)) - self.assertEqual(permission['args'][3], IViewPermission) - self.assertEqual(permission['args'][4], '') - self.assertEqual(permission['args'][5], None) - - regadapt = actions[1] - regadapt_discriminator = ('view', IFoo, '', IRequest, IView) - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerAdapter') - self.assertEqual(regadapt['args'][1], view) - self.assertEqual(regadapt['args'][2], (IFoo, IRequest)) - self.assertEqual(regadapt['args'][3], IView) - self.assertEqual(regadapt['args'][4], '') - self.assertEqual(regadapt['args'][5], None) + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + sm = getSiteManager() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') + self.failUnless(wrapper) + self.failIf(hasattr(wrapper, '__call_permissive__')) + + perm = sm.adapters.lookup((IFoo, IRequest), IViewPermission, name='') + self.assertEqual(perm, None) def test_view_as_function_requestonly(self): + from zope.interface import Interface + from zope.component import getSiteManager + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IViewPermission + context = DummyContext() def view(request): return 'OK' - class IFoo: + class IFoo(Interface): pass self._callFUT(context, 'repoze.view', IFoo, view=view) actions = context.actions - from repoze.bfg.interfaces import IRequest - from repoze.bfg.interfaces import IView - from repoze.bfg.interfaces import IViewPermission - from repoze.bfg.security import ViewPermissionFactory - from repoze.bfg.zcml import handler - - self.assertEqual(len(actions), 2) + self.assertEqual(len(actions), 1) - permission = actions[0] - permission_discriminator = ('permission', IFoo, '', IRequest, - IViewPermission) - self.assertEqual(permission['discriminator'], permission_discriminator) - self.assertEqual(permission['callable'], handler) - self.assertEqual(permission['args'][0], 'registerAdapter') - self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory)) - self.assertEqual(permission['args'][1].permission_name, 'repoze.view') - self.assertEqual(permission['args'][2], (IFoo, IRequest)) - self.assertEqual(permission['args'][3], IViewPermission) - self.assertEqual(permission['args'][4], '') - self.assertEqual(permission['args'][5], None) - - regadapt = actions[1] - regadapt_discriminator = ('view', IFoo, '', IRequest, IView) - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerAdapter') - wrapper = regadapt['args'][1] + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + sm = getSiteManager() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') self.failIfEqual(wrapper, view) self.assertEqual(wrapper.__module__, view.__module__) self.assertEqual(wrapper.__name__, view.__name__) self.assertEqual(wrapper.__doc__, view.__doc__) result = wrapper(None, None) self.assertEqual(result, 'OK') - self.assertEqual(regadapt['args'][2], (IFoo, IRequest)) - self.assertEqual(regadapt['args'][3], IView) - self.assertEqual(regadapt['args'][4], '') - self.assertEqual(regadapt['args'][5], None) + self.failIf(hasattr(wrapper, '__call_permissive__')) + + perm = sm.adapters.lookup((IFoo, IRequest), IViewPermission, name='') + self.assertEqual(perm, None) def test_view_as_instance(self): + from zope.interface import Interface + from zope.component import getSiteManager + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IViewPermission + context = DummyContext() class AView: def __call__(self, context, request): """ """ view = AView() - class IFoo: + class IFoo(Interface): pass self._callFUT(context, 'repoze.view', IFoo, view=view) actions = context.actions - from repoze.bfg.interfaces import IRequest - from repoze.bfg.interfaces import IView - from repoze.bfg.interfaces import IViewPermission - from repoze.bfg.security import ViewPermissionFactory - from repoze.bfg.zcml import handler + self.assertEqual(len(actions), 1) - self.assertEqual(len(actions), 2) + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + sm = getSiteManager() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') + self.failUnless(wrapper) + self.failIf(hasattr(wrapper, '__call_permissive__')) - permission = actions[0] - permission_discriminator = ('permission', IFoo, '', IRequest, - IViewPermission) - self.assertEqual(permission['discriminator'], permission_discriminator) - self.assertEqual(permission['callable'], handler) - self.assertEqual(permission['args'][0], 'registerAdapter') - self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory)) - self.assertEqual(permission['args'][1].permission_name, 'repoze.view') - self.assertEqual(permission['args'][2], (IFoo, IRequest)) - self.assertEqual(permission['args'][3], IViewPermission) - self.assertEqual(permission['args'][4], '') - self.assertEqual(permission['args'][5], None) - - regadapt = actions[1] - regadapt_discriminator = ('view', IFoo, '', IRequest, IView) - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerAdapter') - self.assertEqual(regadapt['args'][1], view) - self.assertEqual(regadapt['args'][2], (IFoo, IRequest)) - self.assertEqual(regadapt['args'][3], IView) - self.assertEqual(regadapt['args'][4], '') - self.assertEqual(regadapt['args'][5], None) + perm = sm.adapters.lookup((IFoo, IRequest), IViewPermission, name='') + self.assertEqual(perm, None) def test_view_as_instance_requestonly(self): + from zope.interface import Interface + from zope.component import getSiteManager + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IViewPermission + context = DummyContext() class AView: def __call__(self, request): return 'OK' view = AView() - class IFoo: + class IFoo(Interface): pass self._callFUT(context, 'repoze.view', IFoo, view=view) actions = context.actions - from repoze.bfg.interfaces import IRequest - from repoze.bfg.interfaces import IView - from repoze.bfg.interfaces import IViewPermission - from repoze.bfg.security import ViewPermissionFactory - from repoze.bfg.zcml import handler - - self.assertEqual(len(actions), 2) + self.assertEqual(len(actions), 1) - permission = actions[0] - permission_discriminator = ('permission', IFoo, '', IRequest, - IViewPermission) - self.assertEqual(permission['discriminator'], permission_discriminator) - self.assertEqual(permission['callable'], handler) - self.assertEqual(permission['args'][0], 'registerAdapter') - self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory)) - self.assertEqual(permission['args'][1].permission_name, 'repoze.view') - self.assertEqual(permission['args'][2], (IFoo, IRequest)) - self.assertEqual(permission['args'][3], IViewPermission) - self.assertEqual(permission['args'][4], '') - self.assertEqual(permission['args'][5], None) - - regadapt = actions[1] - regadapt_discriminator = ('view', IFoo, '', IRequest, IView) - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerAdapter') - wrapper = regadapt['args'][1] + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + sm = getSiteManager() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') self.failIfEqual(wrapper, view) self.assertEqual(wrapper.__module__, view.__module__) self.failUnless('instance' in wrapper.__name__) self.assertEqual(wrapper.__doc__, view.__doc__) result = wrapper(None, None) self.assertEqual(result, 'OK') - self.assertEqual(regadapt['args'][2], (IFoo, IRequest)) - self.assertEqual(regadapt['args'][3], IView) - self.assertEqual(regadapt['args'][4], '') - self.assertEqual(regadapt['args'][5], None) + self.failIf(hasattr(wrapper, '__call_permissive__')) + + perm = sm.adapters.lookup((IFoo, IRequest), IViewPermission, name='') + self.assertEqual(perm, None) def test_view_as_oldstyle_class(self): + from zope.interface import Interface + from zope.component import getSiteManager + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IViewPermission context = DummyContext() - class IFoo: + class IFoo(Interface): pass class view: def __init__(self, context, request): @@ -209,47 +168,36 @@ class TestViewDirective(unittest.TestCase): return self self._callFUT(context, 'repoze.view', IFoo, view=view) actions = context.actions - from repoze.bfg.interfaces import IRequest - from repoze.bfg.interfaces import IView - from repoze.bfg.interfaces import IViewPermission - from repoze.bfg.security import ViewPermissionFactory - from repoze.bfg.zcml import handler - self.assertEqual(len(actions), 2) + self.assertEqual(len(actions), 1) - permission = actions[0] - permission_discriminator = ('permission', IFoo, '', IRequest, - IViewPermission) - self.assertEqual(permission['discriminator'], permission_discriminator) - self.assertEqual(permission['callable'], handler) - self.assertEqual(permission['args'][0], 'registerAdapter') - self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory)) - self.assertEqual(permission['args'][1].permission_name, 'repoze.view') - self.assertEqual(permission['args'][2], (IFoo, IRequest)) - self.assertEqual(permission['args'][3], IViewPermission) - self.assertEqual(permission['args'][4], '') - self.assertEqual(permission['args'][5], None) - - regadapt = actions[1] - regadapt_discriminator = ('view', IFoo, '', IRequest, IView) - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerAdapter') - wrapper = regadapt['args'][1] + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + sm = getSiteManager() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') self.assertEqual(wrapper.__module__, view.__module__) self.assertEqual(wrapper.__name__, view.__name__) self.assertEqual(wrapper.__doc__, view.__doc__) result = wrapper(None, None) self.assertEqual(result.context, None) self.assertEqual(result.request, None) - self.assertEqual(regadapt['args'][2], (IFoo, IRequest)) - self.assertEqual(regadapt['args'][3], IView) - self.assertEqual(regadapt['args'][4], '') - self.assertEqual(regadapt['args'][5], None) + self.failIf(hasattr(wrapper, '__call_permissive__')) + + perm = sm.adapters.lookup((IFoo, IRequest), IViewPermission, name='') + self.assertEqual(perm, None) def test_view_as_oldstyle_class_requestonly(self): + from zope.interface import Interface + from zope.component import getSiteManager + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IViewPermission + context = DummyContext() - class IFoo: + class IFoo(Interface): pass class view: def __init__(self, request): @@ -259,46 +207,35 @@ class TestViewDirective(unittest.TestCase): return self self._callFUT(context, 'repoze.view', IFoo, view=view) actions = context.actions - from repoze.bfg.interfaces import IRequest - from repoze.bfg.interfaces import IView - from repoze.bfg.interfaces import IViewPermission - from repoze.bfg.security import ViewPermissionFactory - from repoze.bfg.zcml import handler - - self.assertEqual(len(actions), 2) + self.assertEqual(len(actions), 1) - permission = actions[0] - permission_discriminator = ('permission', IFoo, '', IRequest, - IViewPermission) - self.assertEqual(permission['discriminator'], permission_discriminator) - self.assertEqual(permission['callable'], handler) - self.assertEqual(permission['args'][0], 'registerAdapter') - self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory)) - self.assertEqual(permission['args'][1].permission_name, 'repoze.view') - self.assertEqual(permission['args'][2], (IFoo, IRequest)) - self.assertEqual(permission['args'][3], IViewPermission) - self.assertEqual(permission['args'][4], '') - self.assertEqual(permission['args'][5], None) - - regadapt = actions[1] - regadapt_discriminator = ('view', IFoo, '', IRequest, IView) - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerAdapter') - wrapper = regadapt['args'][1] + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + sm = getSiteManager() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') self.assertEqual(wrapper.__module__, view.__module__) self.assertEqual(wrapper.__name__, view.__name__) self.assertEqual(wrapper.__doc__, view.__doc__) result = wrapper(None, None) self.assertEqual(result.request, None) - self.assertEqual(regadapt['args'][2], (IFoo, IRequest)) - self.assertEqual(regadapt['args'][3], IView) - self.assertEqual(regadapt['args'][4], '') - self.assertEqual(regadapt['args'][5], None) + self.failIf(hasattr(wrapper, '__call_permissive__')) + + perm = sm.adapters.lookup((IFoo, IRequest), IViewPermission, name='') + self.assertEqual(perm, None) + def test_view_as_newstyle_class(self): + from zope.interface import Interface + from zope.component import getSiteManager + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IViewPermission + context = DummyContext() - class IFoo: + class IFoo(Interface): pass class view(object): def __init__(self, context, request): @@ -309,47 +246,35 @@ class TestViewDirective(unittest.TestCase): return self self._callFUT(context, 'repoze.view', IFoo, view=view) actions = context.actions - from repoze.bfg.interfaces import IRequest - from repoze.bfg.interfaces import IView - from repoze.bfg.interfaces import IViewPermission - from repoze.bfg.security import ViewPermissionFactory - from repoze.bfg.zcml import handler - - self.assertEqual(len(actions), 2) + self.assertEqual(len(actions), 1) - permission = actions[0] - permission_discriminator = ('permission', IFoo, '', IRequest, - IViewPermission) - self.assertEqual(permission['discriminator'], permission_discriminator) - self.assertEqual(permission['callable'], handler) - self.assertEqual(permission['args'][0], 'registerAdapter') - self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory)) - self.assertEqual(permission['args'][1].permission_name, 'repoze.view') - self.assertEqual(permission['args'][2], (IFoo, IRequest)) - self.assertEqual(permission['args'][3], IViewPermission) - self.assertEqual(permission['args'][4], '') - self.assertEqual(permission['args'][5], None) - - regadapt = actions[1] - regadapt_discriminator = ('view', IFoo, '', IRequest, IView) - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerAdapter') - wrapper = regadapt['args'][1] + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + sm = getSiteManager() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') self.assertEqual(wrapper.__module__, view.__module__) self.assertEqual(wrapper.__name__, view.__name__) self.assertEqual(wrapper.__doc__, view.__doc__) result = wrapper(None, None) self.assertEqual(result.context, None) self.assertEqual(result.request, None) - self.assertEqual(regadapt['args'][2], (IFoo, IRequest)) - self.assertEqual(regadapt['args'][3], IView) - self.assertEqual(regadapt['args'][4], '') - self.assertEqual(regadapt['args'][5], None) + self.failIf(hasattr(wrapper, '__call_permissive__')) + + perm = sm.adapters.lookup((IFoo, IRequest), IViewPermission, name='') + self.assertEqual(perm, None) def test_view_as_newstyle_class_requestonly(self): + from zope.interface import Interface + from zope.component import getSiteManager + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IViewPermission + context = DummyContext() - class IFoo: + class IFoo(Interface): pass class view(object): def __init__(self, request): @@ -359,170 +284,470 @@ class TestViewDirective(unittest.TestCase): return self self._callFUT(context, 'repoze.view', IFoo, view=view) actions = context.actions - from repoze.bfg.interfaces import IRequest - from repoze.bfg.interfaces import IView - from repoze.bfg.interfaces import IViewPermission - from repoze.bfg.security import ViewPermissionFactory - from repoze.bfg.zcml import handler - - self.assertEqual(len(actions), 2) + self.assertEqual(len(actions), 1) - permission = actions[0] - permission_discriminator = ('permission', IFoo, '', IRequest, - IViewPermission) - self.assertEqual(permission['discriminator'], permission_discriminator) - self.assertEqual(permission['callable'], handler) - self.assertEqual(permission['args'][0], 'registerAdapter') - self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory)) - self.assertEqual(permission['args'][1].permission_name, 'repoze.view') - self.assertEqual(permission['args'][2], (IFoo, IRequest)) - self.assertEqual(permission['args'][3], IViewPermission) - self.assertEqual(permission['args'][4], '') - self.assertEqual(permission['args'][5], None) - - regadapt = actions[1] - regadapt_discriminator = ('view', IFoo, '', IRequest, IView) - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerAdapter') - wrapper = regadapt['args'][1] + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + sm = getSiteManager() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') self.assertEqual(wrapper.__module__, view.__module__) self.assertEqual(wrapper.__name__, view.__name__) self.assertEqual(wrapper.__doc__, view.__doc__) result = wrapper(None, None) self.assertEqual(result.request, None) - self.assertEqual(regadapt['args'][2], (IFoo, IRequest)) - self.assertEqual(regadapt['args'][3], IView) - self.assertEqual(regadapt['args'][4], '') - self.assertEqual(regadapt['args'][5], None) + self.failIf(hasattr(wrapper, '__call_permissive__')) + + perm = sm.adapters.lookup((IFoo, IRequest), IViewPermission, name='') + self.assertEqual(perm, None) + def test_request_type_asinterface(self): - context = DummyContext() - class IFoo: + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IViewPermission + class IRequest(Interface): + pass + class IFoo(Interface): pass + context = DummyContext(IRequest) view = lambda *arg: None self._callFUT(context, 'repoze.view', IFoo, view=view, request_type=IDummy) actions = context.actions + + self.assertEqual(len(actions), 1) + + action = actions[0] + discrim = ('view', IFoo, '', IDummy, IView, None, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + sm = getSiteManager() + wrapper = sm.adapters.lookup((IFoo, IDummy), IView, name='') + self.failUnless(wrapper) + self.failIf(hasattr(wrapper, '__call_permissive__')) + + perm = sm.adapters.lookup((IFoo, IRequest), IViewPermission, name='') + self.assertEqual(perm, None) + + def test_request_type_as_noninterface(self): + from zope.component import getSiteManager + from zope.interface import Interface + from zope.interface import implementedBy from repoze.bfg.interfaces import IView - from repoze.bfg.interfaces import IViewPermission - from repoze.bfg.security import ViewPermissionFactory - from repoze.bfg.zcml import handler + class IFoo(Interface): + pass + class Dummy: + pass + context = DummyContext(Dummy) + view = lambda *arg: None + self._callFUT(context, 'repoze.view', IFoo, view=view, + request_type=Dummy) + actions = context.actions - self.assertEqual(len(actions), 2) + self.assertEqual(len(actions), 1) - permission = actions[0] - permission_discriminator = ('permission', IFoo, '', DummyModule, - IViewPermission) - self.assertEqual(permission['discriminator'], permission_discriminator) - self.assertEqual(permission['callable'], handler) - self.assertEqual(permission['args'][0], 'registerAdapter') - self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory)) - self.assertEqual(permission['args'][1].permission_name, 'repoze.view') - self.assertEqual(permission['args'][2], (IFoo, DummyModule)) - self.assertEqual(permission['args'][3], IViewPermission) - self.assertEqual(permission['args'][4], '') - self.assertEqual(permission['args'][5], None) - - regadapt = actions[1] - regadapt_discriminator = ('view', IFoo, '', DummyModule, IView) - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerAdapter') - self.assertEqual(regadapt['args'][1], view) - self.assertEqual(regadapt['args'][2], (IFoo, DummyModule)) - self.assertEqual(regadapt['args'][3], IView) - self.assertEqual(regadapt['args'][4], '') - self.assertEqual(regadapt['args'][5], None) + action = actions[0] + discrim = ('view', IFoo, '', Dummy, IView, None, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + sm = getSiteManager() + iface = implementedBy(Dummy) + wrapper = sm.adapters.lookup((IFoo, iface), IView, name='') + self.failUnless(wrapper) + self.failIf(hasattr(wrapper, '__call_permissive__')) def test_request_type_ashttpmethod(self): + from zope.interface import Interface + from zope.component import getSiteManager + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRequest context = DummyContext() - class IFoo: + class IFoo(Interface): pass view = lambda *arg: None self._callFUT(context, 'repoze.view', IFoo, view=view, request_type='GET') actions = context.actions - from repoze.bfg.interfaces import IView - from repoze.bfg.interfaces import IGETRequest - self.assertEqual(len(actions), 2) + self.assertEqual(len(actions), 1) - permission = actions[0] - self.assertEqual(permission['args'][2], (IFoo, IGETRequest)) - regadapt = actions[1] - regadapt_discriminator = ('view', IFoo, '', IDummy, IView) - self.assertEqual(regadapt['args'][2], (IFoo, IGETRequest)) + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, None, 'GET', None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + sm = getSiteManager() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') + request = DummyRequest() + request.method = 'GET' + self.assertEqual(wrapper.__predicated__(None, request), True) + request.method = 'POST' + self.assertEqual(wrapper.__predicated__(None, request), False) + def test_request_type_asinterfacestring(self): - context = DummyContext() - class IFoo: + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IViewPermission + class IFoo(Interface): + pass + class IRequest(Interface): pass + context = DummyContext(IRequest) view = lambda *arg: None self._callFUT(context, 'repoze.view', IFoo, view=view, request_type='whatever') actions = context.actions - from repoze.bfg.interfaces import IView - self.assertEqual(len(actions), 2) + self.assertEqual(len(actions), 1) - permission = actions[0] - self.assertEqual(permission['args'][2], (IFoo, DummyModule)) - regadapt = actions[1] - regadapt_discriminator = ('view', IFoo, '', DummyModule, IView) - self.assertEqual(regadapt['args'][2], (IFoo, DummyModule)) + discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None) + self.assertEqual(actions[0]['discriminator'], discrim) + register = actions[0]['callable'] + register() + sm = getSiteManager() + regview = sm.adapters.lookup((IFoo, IRequest), IView, name='') + self.assertEqual(view, regview) + self.failIf(hasattr(view, '__call_permissive__')) + + perm = sm.adapters.lookup((IFoo, IRequest), IViewPermission, name='') + self.assertEqual(perm, None) def test_with_route_name(self): + from zope.interface import Interface + from zope.interface import implementedBy + from zope.component import getSiteManager + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRouteRequest + class IFoo(Interface): + pass + context = DummyContext() + view = lambda *arg: '123' + self._callFUT(context, 'repoze.view', IFoo, view=view, route_name='foo') + actions = context.actions + + self.assertEqual(len(actions), 1) + + action = actions[0] + register = action['callable'] + register() + sm = getSiteManager() + factory = sm.getUtility(IRouteRequest, 'foo') + request_type = implementedBy(factory) + discrim = ('view', IFoo, '', request_type, IView, None, None, None, + 'foo') + self.assertEqual(action['discriminator'], discrim) + the_view = sm.adapters.lookup((IFoo, request_type), IView, name='') + request = factory({}) + self.assertEqual(the_view(None, request), '123') + + def test_with_request_method_true(self): from zope.component import getSiteManager - from repoze.bfg.interfaces import IRequestFactories - class IFoo: + from zope.interface import Interface + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + context = DummyContext() + class IFoo(Interface): pass - class IDummyRequest: + view = lambda *arg: None + sm = getSiteManager() + def view(context, request): + return '123' + self._callFUT(context, None, IFoo, view=view, request_method='POST') + actions = context.actions + self.assertEqual(len(actions), 1) + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, None, 'POST', None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') + request = DummyRequest() + request.method = 'POST' + self.assertEqual(wrapper(None, request), '123') + + def test_with_request_method_false(self): + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.view import NotFound + context = DummyContext() + class IFoo(Interface): pass + view = lambda *arg: None + sm = getSiteManager() + def view(context, request): + """ """ + self._callFUT(context, None, IFoo, view=view, request_method='POST') + actions = context.actions + self.assertEqual(len(actions), 1) + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, None, 'POST', None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') + request = DummyRequest() + request.method = 'GET' + self.assertRaises(NotFound, wrapper, None, request) + + def test_with_request_param_noval_true(self): + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView context = DummyContext() - factories = {None:{'interface':IDummyRequest}} + class IFoo(Interface): + pass + view = lambda *arg: None sm = getSiteManager() - sm.registerUtility(factories, IRequestFactories, name='foo') + def view(context, request): + return '123' + self._callFUT(context, None, IFoo, view=view, request_param='abc') + actions = context.actions + self.assertEqual(len(actions), 1) + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, 'abc', None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') + request = DummyRequest() + request.params = {'abc':''} + self.assertEqual(wrapper(None, request), '123') + + def test_with_request_param_noval_false(self): + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.view import NotFound + context = DummyContext() + class IFoo(Interface): + pass view = lambda *arg: None - self._callFUT(context, 'repoze.view', IFoo, view=view, route_name='foo') + sm = getSiteManager() + def view(context, request): + """ """ + self._callFUT(context, None, IFoo, view=view, request_param='abc') + actions = context.actions + self.assertEqual(len(actions), 1) + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, 'abc', None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') + request = DummyRequest() + request.params = {} + self.assertRaises(NotFound, wrapper, None, request) + + def test_with_request_paramoval_true(self): + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + context = DummyContext() + class IFoo(Interface): + pass + view = lambda *arg: None + sm = getSiteManager() + def view(context, request): + return '123' + self._callFUT(context, None, IFoo, view=view, request_param='abc=123') + actions = context.actions + self.assertEqual(len(actions), 1) + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, 'abc', None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') + request = DummyRequest() + request.params = {'abc':'123'} + self.assertEqual(wrapper(None, request), '123') + + def test_with_request_param_val_false(self): + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.view import NotFound + context = DummyContext() + class IFoo(Interface): + pass + view = lambda *arg: None + sm = getSiteManager() + def view(context, request): + """ """ + self._callFUT(context, None, IFoo, view=view, request_param='abc=123') + actions = context.actions + self.assertEqual(len(actions), 1) + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, 'abc', None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') + request = DummyRequest() + request.params = {'abc':'456'} + self.assertRaises(NotFound, wrapper, None, request) + + def test_with_containment_true(self): + from zope.component import getSiteManager + from zope.interface import directlyProvides + from zope.interface import Interface + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + context = DummyContext() + class IFoo(Interface): + pass + view = lambda *arg: None + sm = getSiteManager() + def view(context, request): + return '123' + sm.registerAdapter(view, (IFoo, IRequest), IView, name='') + self._callFUT(context, None, IFoo, view=view, containment=IFoo) + actions = context.actions + self.assertEqual(len(actions), 1) + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, IFoo, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') + request = DummyRequest() + context = Dummy() + directlyProvides(context, IFoo) + self.assertEqual(wrapper(context, request), '123') + + def test_with_containment_false(self): + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.view import NotFound + context = DummyContext() + class IFoo(Interface): + pass + view = lambda *arg: None + sm = getSiteManager() + def view(context, request): + """ """ + self._callFUT(context, None, IFoo, view=view, containment=IFoo) actions = context.actions + self.assertEqual(len(actions), 1) + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, IFoo, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') + request = DummyRequest() + context = Dummy() + self.assertRaises(NotFound, wrapper, context, request) + + def test_multiview_replaces_traditional_view(self): + from zope.component import getSiteManager + from zope.interface import Interface + from repoze.bfg.interfaces import IRequest from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IMultiView from repoze.bfg.interfaces import IViewPermission - from repoze.bfg.security import ViewPermissionFactory - from repoze.bfg.zcml import handler - - self.assertEqual(len(actions), 2) - - permission = actions[0] - permission_discriminator = ('permission', IFoo, '', IDummyRequest, - IViewPermission) - self.assertEqual(permission['discriminator'], permission_discriminator) - self.assertEqual(permission['callable'], handler) - self.assertEqual(permission['args'][0], 'registerAdapter') - self.failUnless(isinstance(permission['args'][1],ViewPermissionFactory)) - self.assertEqual(permission['args'][1].permission_name, 'repoze.view') - self.assertEqual(permission['args'][2], (IFoo, IDummyRequest)) - self.assertEqual(permission['args'][3], IViewPermission) - self.assertEqual(permission['args'][4], '') - self.assertEqual(permission['args'][5], None) - - regadapt = actions[1] - regadapt_discriminator = ('view', IFoo, '', IDummyRequest, IView) - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerAdapter') - self.assertEqual(regadapt['args'][1], view) - self.assertEqual(regadapt['args'][2], (IFoo, IDummyRequest)) - self.assertEqual(regadapt['args'][3], IView) - self.assertEqual(regadapt['args'][4], '') - self.assertEqual(regadapt['args'][5], None) - - def test_with_route_name_bad_order(self): context = DummyContext() - context.request_factories = {} + class IFoo(Interface): + pass view = lambda *arg: None - from zope.configuration.exceptions import ConfigurationError - self.assertRaises(ConfigurationError, self._callFUT, context, - 'repoze.view', None, view, '', None, 'foo') + sm = getSiteManager() + def view(context, request): + return '123' + sm.registerAdapter(view, (IFoo, IRequest), IView, name='') + self._callFUT(context, 'repoze.view', IFoo, view=view) + actions = context.actions + self.assertEqual(len(actions), 1) + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') + self.failUnless(IMultiView.providedBy(wrapper)) + perm = sm.adapters.lookup((IFoo, IRequest), IViewPermission, name='') + self.assertEqual(perm, wrapper.__permitted__) + self.assertEqual(wrapper(None, None), '123') + + def test_multiview_replaces_multiview(self): + from zope.component import getSiteManager + from zope.interface import Interface + from zope.interface import implements + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IMultiView + from repoze.bfg.interfaces import IViewPermission + context = DummyContext() + class IFoo(Interface): + pass + view = lambda *arg: None + sm = getSiteManager() + class DummyMultiView: + implements(IMultiView) + def __init__(self): + self.views = [] + self.name = 'name' + def add(self, view, score): + self.views.append(view) + def __call__(self, context, request): + return '123' + def __permitted__(self, context, request): + """ """ + view = DummyMultiView() + sm.registerAdapter(view, (IFoo, IRequest), IMultiView, name='') + def view2(context, request): + """ """ + self._callFUT(context, None, IFoo, view=view2) + actions = context.actions + self.assertEqual(len(actions), 1) + action = actions[0] + discrim = ('view', IFoo, '', IRequest, IView, None, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + wrapper = sm.adapters.lookup((IFoo, IRequest), IView, name='') + perm = sm.adapters.lookup((IFoo, IRequest), IViewPermission, name='') + self.assertEqual(perm, wrapper.__permitted__) + self.failUnless(IMultiView.providedBy(wrapper)) + self.assertEqual(wrapper(None, None), '123') + self.assertEqual(wrapper.views, [view2]) + + def test_for_as_class(self): + from zope.component import getSiteManager + from zope.interface import implementedBy + from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView + context = DummyContext() + class Foo: + pass + sm = getSiteManager() + def view(context, request): + """ """ + self._callFUT(context, 'repoze.view', Foo, view=view) + actions = context.actions + self.assertEqual(len(actions), 1) + action = actions[0] + discrim = ('view', Foo, '', IRequest, IView, None, None, None, None) + self.assertEqual(action['discriminator'], discrim) + register = action['callable'] + register() + foo = implementedBy(Foo) + wrapper = sm.adapters.lookup((foo, IRequest), IView, name='') + self.assertEqual(wrapper, view) class TestNotFoundDirective(unittest.TestCase): def _callFUT(self, context, view): @@ -530,27 +755,24 @@ class TestNotFoundDirective(unittest.TestCase): return notfound(context, view) def test_it(self): + from zope.component import getSiteManager + from repoze.bfg.interfaces import INotFoundView + context = DummyContext() def view(request): return 'OK' self._callFUT(context, view) actions = context.actions - from repoze.bfg.interfaces import INotFoundView - from repoze.bfg.zcml import handler - self.assertEqual(len(actions), 1) regadapt = actions[0] - regadapt_discriminator = ('notfound_view',) - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerUtility') - derived_view = regadapt['args'][1] + self.assertEqual(regadapt['discriminator'], INotFoundView) + register = regadapt['callable'] + register() + sm = getSiteManager() + derived_view = sm.getUtility(INotFoundView) self.assertEqual(derived_view(None, None), 'OK') self.assertEqual(derived_view.__name__, view.__name__) - self.assertEqual(regadapt['args'][2], INotFoundView) - self.assertEqual(regadapt['args'][3], '') - self.assertEqual(regadapt['args'][4], None) class TestForbiddenDirective(unittest.TestCase): def _callFUT(self, context, view): @@ -558,155 +780,143 @@ class TestForbiddenDirective(unittest.TestCase): return forbidden(context, view) def test_it(self): + from zope.component import getSiteManager context = DummyContext() def view(request): return 'OK' self._callFUT(context, view) actions = context.actions from repoze.bfg.interfaces import IForbiddenView - from repoze.bfg.zcml import handler self.assertEqual(len(actions), 1) regadapt = actions[0] - regadapt_discriminator = ('notfound_view',) - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerUtility') - derived_view = regadapt['args'][1] + self.assertEqual(regadapt['discriminator'], IForbiddenView) + register = regadapt['callable'] + register() + sm = getSiteManager() + derived_view = sm.getUtility(IForbiddenView) self.assertEqual(derived_view(None, None), 'OK') self.assertEqual(derived_view.__name__, view.__name__) - self.assertEqual(regadapt['args'][2], IForbiddenView) - self.assertEqual(regadapt['args'][3], '') - self.assertEqual(regadapt['args'][4], None) class TestRepozeWho1AuthenticationPolicyDirective(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + def _callFUT(self, context, **kw): from repoze.bfg.zcml import repozewho1authenticationpolicy return repozewho1authenticationpolicy(context, **kw) def test_it_defaults(self): + from zope.component import getUtility + from repoze.bfg.interfaces import IAuthenticationPolicy context = DummyContext() self._callFUT(context) actions = context.actions - from repoze.bfg.interfaces import IAuthenticationPolicy - from repoze.bfg.zcml import handler - self.assertEqual(len(actions), 1) - regadapt = actions[0] - regadapt_discriminator = 'authentication_policy' - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerUtility') - policy = regadapt['args'][1] + self.assertEqual(regadapt['discriminator'], IAuthenticationPolicy) + self.assertEqual(regadapt['callable'], None) + self.assertEqual(regadapt['args'], ()) + policy = getUtility(IAuthenticationPolicy) self.assertEqual(policy.callback, None) self.assertEqual(policy.identifier_name, 'auth_tkt') - self.assertEqual(regadapt['args'][2], IAuthenticationPolicy) - self.assertEqual(regadapt['args'][3], '') - self.assertEqual(regadapt['args'][4], None) def test_it(self): + from zope.component import getUtility + from repoze.bfg.interfaces import IAuthenticationPolicy context = DummyContext() def callback(identity, request): """ """ self._callFUT(context, identifier_name='something', callback=callback) actions = context.actions - from repoze.bfg.interfaces import IAuthenticationPolicy - from repoze.bfg.zcml import handler - self.assertEqual(len(actions), 1) - regadapt = actions[0] - regadapt_discriminator = 'authentication_policy' - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerUtility') - policy = regadapt['args'][1] + self.assertEqual(regadapt['discriminator'], IAuthenticationPolicy) + self.assertEqual(regadapt['callable'], None) + self.assertEqual(regadapt['args'], ()) + policy = getUtility(IAuthenticationPolicy) self.assertEqual(policy.callback, callback) self.assertEqual(policy.identifier_name, 'something') - self.assertEqual(regadapt['args'][2], IAuthenticationPolicy) - self.assertEqual(regadapt['args'][3], '') - self.assertEqual(regadapt['args'][4], None) class TestRemoteUserAuthenticationPolicyDirective(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + def _callFUT(self, context, **kw): from repoze.bfg.zcml import remoteuserauthenticationpolicy return remoteuserauthenticationpolicy(context, **kw) def test_defaults(self): + from repoze.bfg.interfaces import IAuthenticationPolicy + from zope.component import getUtility context = DummyContext() def callback(identity, request): """ """ self._callFUT(context) actions = context.actions - from repoze.bfg.interfaces import IAuthenticationPolicy - from repoze.bfg.zcml import handler - self.assertEqual(len(actions), 1) - regadapt = actions[0] regadapt_discriminator = 'authentication_policy' - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerUtility') - policy = regadapt['args'][1] + self.assertEqual(regadapt['discriminator'], IAuthenticationPolicy) + self.assertEqual(regadapt['callable'], None) + self.assertEqual(regadapt['args'], ()) + policy = getUtility(IAuthenticationPolicy) self.assertEqual(policy.environ_key, 'REMOTE_USER') self.assertEqual(policy.callback, None) - self.assertEqual(regadapt['args'][2], IAuthenticationPolicy) - self.assertEqual(regadapt['args'][3], '') - self.assertEqual(regadapt['args'][4], None) def test_it(self): + from zope.component import getUtility + from repoze.bfg.interfaces import IAuthenticationPolicy context = DummyContext() def callback(identity, request): """ """ self._callFUT(context, environ_key='BLAH', callback=callback) actions = context.actions - from repoze.bfg.interfaces import IAuthenticationPolicy - from repoze.bfg.zcml import handler - self.assertEqual(len(actions), 1) - regadapt = actions[0] - regadapt_discriminator = 'authentication_policy' - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerUtility') - policy = regadapt['args'][1] + self.assertEqual(regadapt['discriminator'], IAuthenticationPolicy) + self.assertEqual(regadapt['callable'], None) + self.assertEqual(regadapt['args'], ()) + policy = getUtility(IAuthenticationPolicy) self.assertEqual(policy.environ_key, 'BLAH') self.assertEqual(policy.callback, callback) - self.assertEqual(regadapt['args'][2], IAuthenticationPolicy) - self.assertEqual(regadapt['args'][3], '') - self.assertEqual(regadapt['args'][4], None) class TestAuthTktAuthenticationPolicyDirective(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + def _callFUT(self, context, secret, **kw): from repoze.bfg.zcml import authtktauthenticationpolicy return authtktauthenticationpolicy(context, secret, **kw) def test_it_defaults(self): + from zope.component import getUtility + from repoze.bfg.interfaces import IAuthenticationPolicy context = DummyContext() self._callFUT(context, 'sosecret') actions = context.actions - from repoze.bfg.interfaces import IAuthenticationPolicy - from repoze.bfg.zcml import handler - self.assertEqual(len(actions), 1) - regadapt = actions[0] - regadapt_discriminator = 'authentication_policy' - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerUtility') - policy = regadapt['args'][1] + self.assertEqual(regadapt['discriminator'], IAuthenticationPolicy) + self.assertEqual(regadapt['callable'], None) + self.assertEqual(regadapt['args'], ()) + policy = getUtility(IAuthenticationPolicy) self.assertEqual(policy.cookie.secret, 'sosecret') self.assertEqual(policy.callback, None) - self.assertEqual(regadapt['args'][2], IAuthenticationPolicy) - self.assertEqual(regadapt['args'][3], '') - self.assertEqual(regadapt['args'][4], None) def test_it_noconfigerror(self): + from zope.component import getUtility + from repoze.bfg.interfaces import IAuthenticationPolicy context = DummyContext() def callback(identity, request): """ """ @@ -715,22 +925,14 @@ class TestAuthTktAuthenticationPolicyDirective(unittest.TestCase): secure=True, include_ip=True, timeout=100, reissue_time=60) actions = context.actions - from repoze.bfg.interfaces import IAuthenticationPolicy - from repoze.bfg.zcml import handler - self.assertEqual(len(actions), 1) - regadapt = actions[0] - regadapt_discriminator = 'authentication_policy' - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerUtility') - policy = regadapt['args'][1] + self.assertEqual(regadapt['discriminator'], IAuthenticationPolicy) + self.assertEqual(regadapt['callable'], None) + self.assertEqual(regadapt['args'], ()) + policy = getUtility(IAuthenticationPolicy) self.assertEqual(policy.cookie.secret, 'sosecret') self.assertEqual(policy.callback, callback) - self.assertEqual(regadapt['args'][2], IAuthenticationPolicy) - self.assertEqual(regadapt['args'][3], '') - self.assertEqual(regadapt['args'][4], None) def test_it_configerror(self): from zope.configuration.exceptions import ConfigurationError @@ -745,43 +947,74 @@ class TestAuthTktAuthenticationPolicyDirective(unittest.TestCase): reissue_time=500) class TestACLAuthorizationPolicyDirective(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + def _callFUT(self, context, **kw): from repoze.bfg.zcml import aclauthorizationpolicy return aclauthorizationpolicy(context, **kw) def test_it(self): + from zope.component import getUtility from repoze.bfg.authorization import ACLAuthorizationPolicy from repoze.bfg.interfaces import IAuthorizationPolicy - from repoze.bfg.zcml import handler context = DummyContext() def callback(identity, request): """ """ self._callFUT(context) actions = context.actions - self.assertEqual(len(actions), 1) - regadapt = actions[0] - regadapt_discriminator = 'authorization_policy' - self.assertEqual(regadapt['discriminator'], regadapt_discriminator) - self.assertEqual(regadapt['callable'], handler) - self.assertEqual(regadapt['args'][0], 'registerUtility') - policy = regadapt['args'][1] + self.assertEqual(regadapt['discriminator'], IAuthorizationPolicy) + self.assertEqual(regadapt['callable'], None) + self.assertEqual(regadapt['args'], ()) + policy = getUtility(IAuthorizationPolicy) self.assertEqual(policy.__class__, ACLAuthorizationPolicy) - self.assertEqual(regadapt['args'][2], IAuthorizationPolicy) - self.assertEqual(regadapt['args'][3], '') - self.assertEqual(regadapt['args'][4], None) class TestDeriveView(unittest.TestCase): - def _callFUT(self, view): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _callFUT(self, view, *arg, **kw): from repoze.bfg.zcml import derive_view - return derive_view(view) + return derive_view(view, *arg, **kw) + + def _registerLogger(self): + from repoze.bfg.interfaces import ILogger + from zope.component import getSiteManager + logger = DummyLogger() + sm = getSiteManager() + sm.registerUtility(logger, ILogger, 'repoze.bfg.debug') + return logger + + def _registerSettings(self, **d): + from repoze.bfg.interfaces import ISettings + from zope.component import getSiteManager + settings = DummySettings(d) + sm = getSiteManager() + sm.registerUtility(settings, ISettings) + + def _registerSecurityPolicy(self, permissive): + from repoze.bfg.interfaces import IAuthenticationPolicy + from repoze.bfg.interfaces import IAuthorizationPolicy + from zope.component import getSiteManager + policy = DummySecurityPolicy(permissive) + sm = getSiteManager() + sm.registerUtility(policy, IAuthenticationPolicy) + sm.registerUtility(policy, IAuthorizationPolicy) def test_view_as_function_context_and_request(self): def view(context, request): return 'OK' result = self._callFUT(view) self.failUnless(result is view) + self.failIf(hasattr(result, '__call_permissive__')) self.assertEqual(view(None, None), 'OK') def test_view_as_function_requestonly(self): @@ -792,6 +1025,7 @@ class TestDeriveView(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), 'OK') def test_view_as_newstyle_class_context_and_request(self): @@ -805,6 +1039,7 @@ class TestDeriveView(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), 'OK') def test_view_as_newstyle_class_requestonly(self): @@ -818,6 +1053,7 @@ class TestDeriveView(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), 'OK') def test_view_as_oldstyle_class_context_and_request(self): @@ -831,6 +1067,7 @@ class TestDeriveView(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), 'OK') def test_view_as_oldstyle_class_requestonly(self): @@ -844,6 +1081,7 @@ class TestDeriveView(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), 'OK') def test_view_as_instance_context_and_request(self): @@ -853,6 +1091,7 @@ class TestDeriveView(unittest.TestCase): view = View() result = self._callFUT(view) self.failUnless(result is view) + self.failIf(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), 'OK') def test_view_as_instance_requestonly(self): @@ -865,8 +1104,158 @@ class TestDeriveView(unittest.TestCase): self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.failUnless('instance' in result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), 'OK') + def test_view_with_debug_authorization_no_authpol(self): + def view(context, request): + return 'OK' + self._registerSettings(debug_authorization=True) + logger = self._registerLogger() + result = self._callFUT(view, permission='view') + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) + request = DummyRequest() + request.view_name = 'view_name' + request.url = 'url' + self.assertEqual(result(None, request), 'OK') + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], + "debug_authorization of url url (view name " + "'view_name' against context None): Allowed " + "(no authorization policy in use)") + + def test_view_with_debug_authorization_no_permission(self): + def view(context, request): + return 'OK' + self._registerSettings(debug_authorization=True) + self._registerSecurityPolicy(True) + logger = self._registerLogger() + result = self._callFUT(view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) + request = DummyRequest() + request.view_name = 'view_name' + request.url = 'url' + self.assertEqual(result(None, request), 'OK') + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], + "debug_authorization of url url (view name " + "'view_name' against context None): Allowed (" + "no permission registered)") + + def test_view_with_debug_authorization_permission_authpol_permitted(self): + def view(context, request): + return 'OK' + self._registerSettings(debug_authorization=True) + logger = self._registerLogger() + self._registerSecurityPolicy(True) + result = self._callFUT(view, permission='view') + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.assertEqual(result.__call_permissive__, view) + request = DummyRequest() + request.view_name = 'view_name' + request.url = 'url' + self.assertEqual(result(None, request), 'OK') + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], + "debug_authorization of url url (view name " + "'view_name' against context None): True") + + def test_view_with_debug_authorization_permission_authpol_denied(self): + from repoze.bfg.security import Unauthorized + def view(context, request): + """ """ + self._registerSettings(debug_authorization=True) + logger = self._registerLogger() + self._registerSecurityPolicy(False) + result = self._callFUT(view, permission='view') + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.assertEqual(result.__call_permissive__, view) + request = DummyRequest() + request.view_name = 'view_name' + request.url = 'url' + self.assertRaises(Unauthorized, result, None, request) + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], + "debug_authorization of url url (view name " + "'view_name' against context None): False") + + def test_view_with_debug_authorization_permission_authpol_denied2(self): + def view(context, request): + """ """ + self._registerSettings(debug_authorization=True) + logger = self._registerLogger() + self._registerSecurityPolicy(False) + result = self._callFUT(view, permission='view') + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + request = DummyRequest() + request.view_name = 'view_name' + request.url = 'url' + permitted = result.__permitted__(None, None) + self.assertEqual(permitted, False) + + def test_view_with_predicates_all(self): + def view(context, request): + return '123' + predicates = [] + def predicate1(context, request): + predicates.append(True) + return True + def predicate2(context, request): + predicates.append(True) + return True + result = self._callFUT(view, predicates=[predicate1, predicate2]) + request = DummyRequest() + request.method = 'POST' + next = result(None, None) + self.assertEqual(next, '123') + self.assertEqual(predicates, [True, True]) + + def test_view_with_predicates_notall(self): + from repoze.bfg.view import NotFound + def view(context, request): + """ """ + predicates = [] + def predicate1(context, request): + predicates.append(True) + return True + def predicate2(context, request): + predicates.append(True) + return False + result = self._callFUT(view, predicates=[predicate1, predicate2]) + request = DummyRequest() + request.method = 'POST' + self.assertRaises(NotFound, result, None, None) + self.assertEqual(predicates, [True, True]) + + def test_view_with_predicates_checker(self): + def view(context, request): + """ """ + predicates = [] + def predicate1(context, request): + predicates.append(True) + return True + def predicate2(context, request): + predicates.append(True) + return True + result = self._callFUT(view, predicates=[predicate1, predicate2]) + request = DummyRequest() + request.method = 'POST' + next = result.__predicated__(None, None) + self.assertEqual(next, True) + self.assertEqual(predicates, [True, True]) + class TestConnectRouteFunction(unittest.TestCase): def setUp(self): cleanUp() @@ -903,103 +1292,119 @@ class TestRouteDirective(unittest.TestCase): return route(*arg, **kw) def test_defaults(self): - from zope.component import getUtility - from repoze.bfg.interfaces import IRequestFactories + from repoze.bfg.zcml import connect_route context = DummyContext() self._callFUT(context, 'name', 'path') - self.failUnless(getUtility(IRequestFactories, name='name')) + actions = context.actions + self.assertEqual(len(actions), 1) + + route_action = actions[0] + route_callable = route_action['callable'] + route_discriminator = route_action['discriminator'] + route_args = route_action['args'] + self.assertEqual(route_callable, connect_route) + self.assertEqual(len(route_discriminator), 2) + self.assertEqual(route_discriminator[0], 'route') + self.assertEqual(route_discriminator[1], 'name') + self.assertEqual(route_args, ('path', 'name', None)) def test_with_view(self): - from zope.component import getUtility - from repoze.bfg.interfaces import IRequestFactories - from repoze.bfg.zcml import handler + from zope.interface import Interface + from zope.interface import implementedBy + from zope.component import getSiteManager + from repoze.bfg.interfaces import IRouteRequest from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView context = DummyContext() - view = Dummy() + def view(context, request): + return '123' self._callFUT(context, 'name', 'path', view=view) actions = context.actions self.assertEqual(len(actions), 2) - factories = getUtility(IRequestFactories, name='name') - request_iface = factories[None]['interface'] - view_action = actions[0] - view_callable = view_action['callable'] + register = view_action['callable'] + register() + sm = getSiteManager() + request_factory = sm.getUtility(IRouteRequest, 'name') + request_type = implementedBy(request_factory) view_discriminator = view_action['discriminator'] - view_args = view_action['args'] - self.assertEqual(view_callable, handler) - self.assertEqual(len(view_discriminator), 5) + self.assertEqual(len(view_discriminator), 9) self.assertEqual(view_discriminator[0], 'view') self.assertEqual(view_discriminator[1], None) self.assertEqual(view_discriminator[2],'') - self.assertEqual(view_discriminator[3], request_iface) + self.assertEqual(view_discriminator[3], request_type) self.assertEqual(view_discriminator[4], IView) - self.assertEqual(view_args, ('registerAdapter', view, - (None, request_iface), IView, - '', None)) + self.assertEqual(view_discriminator[5], None) + self.assertEqual(view_discriminator[6], None) + self.assertEqual(view_discriminator[7], None) + self.assertEqual(view_discriminator[8], 'name') + register = view_action['callable'] + register() + sm = getSiteManager() + wrapped = sm.adapters.lookup((Interface, request_type), IView, name='') + request = DummyRequest() + self.assertEqual(wrapped(None, request), '123') route_action = actions[1] route_callable = route_action['callable'] route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 4) + self.assertEqual(len(route_discriminator), 2) self.assertEqual(route_discriminator[0], 'route') self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_discriminator[2], None) - self.assertEqual(route_discriminator[3], None) self.assertEqual(route_args, ('path', 'name', None)) def test_with_view_and_view_for(self): - from zope.component import getUtility - from repoze.bfg.interfaces import IRequestFactories - from repoze.bfg.zcml import handler + from zope.component import getSiteManager + from zope.interface import implementedBy from repoze.bfg.zcml import connect_route from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRouteRequest context = DummyContext() - view = Dummy() + def view(context, request): + return '123' self._callFUT(context, 'name', 'path', view=view, view_for=IDummy) actions = context.actions self.assertEqual(len(actions), 2) - factories = getUtility(IRequestFactories, 'name') - request_iface = factories[None]['interface'] - view_action = actions[0] - view_callable = view_action['callable'] + register = view_action['callable'] + register() + sm = getSiteManager() + request_factory = sm.getUtility(IRouteRequest, 'name') + request_type = implementedBy(request_factory) view_discriminator = view_action['discriminator'] - view_args = view_action['args'] - self.assertEqual(view_callable, handler) - self.assertEqual(len(view_discriminator), 5) + self.assertEqual(len(view_discriminator), 9) self.assertEqual(view_discriminator[0], 'view') self.assertEqual(view_discriminator[1], IDummy) self.assertEqual(view_discriminator[2],'') - self.assertEqual(view_discriminator[3], request_iface) - self.assertEqual(view_discriminator[4], IView) - self.assertEqual(view_args, ('registerAdapter', view, - (IDummy, request_iface), IView, - '', None)) + self.assertEqual(view_discriminator[3], request_type) + self.assertEqual(view_discriminator[4], IView) + self.assertEqual(view_discriminator[5], None) + self.assertEqual(view_discriminator[6], None) + self.assertEqual(view_discriminator[7], None) + self.assertEqual(view_discriminator[8], 'name') + wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') + request = DummyRequest() + self.assertEqual(wrapped(None, request), '123') route_action = actions[1] route_callable = route_action['callable'] route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 4) + self.assertEqual(len(route_discriminator), 2) self.assertEqual(route_discriminator[0], 'route') self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_discriminator[2], IDummy) - self.assertEqual(route_discriminator[3], None) self.assertEqual(route_args, ('path', 'name', None,)) def test_without_view(self): from repoze.bfg.zcml import connect_route - context = DummyContext() - view = Dummy() self._callFUT(context, 'name', 'path') actions = context.actions self.assertEqual(len(actions), 1) @@ -1009,32 +1414,275 @@ class TestRouteDirective(unittest.TestCase): route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 4) + self.assertEqual(len(route_discriminator), 2) self.assertEqual(route_discriminator[0], 'route') self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_discriminator[2], None) - self.assertEqual(route_discriminator[3], None) self.assertEqual(route_args, ('path', 'name', None)) - def test_with_request_type(self): + def test_with_view_request_type(self): + from zope.component import getSiteManager + from zope.interface import implementedBy from repoze.bfg.zcml import connect_route + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRouteRequest context = DummyContext() - view = Dummy() - self._callFUT(context, 'name', 'path', request_type="GET") + def view(context, request): + """ """ + self._callFUT(context, 'name', 'path', view=view, + view_request_type="GET") actions = context.actions - self.assertEqual(len(actions), 1) + self.assertEqual(len(actions), 2) - route_action = actions[0] + view_action = actions[0] + register = view_action['callable'] + register() + sm = getSiteManager() + request_factory = sm.getUtility(IRouteRequest, 'name') + request_type = implementedBy(request_factory) + view_discriminator = view_action['discriminator'] + self.assertEqual(len(view_discriminator), 9) + self.assertEqual(view_discriminator[0], 'view') + self.assertEqual(view_discriminator[1], None) + self.assertEqual(view_discriminator[2],'') + self.assertEqual(view_discriminator[3], request_type) + self.assertEqual(view_discriminator[4], IView) + self.assertEqual(view_discriminator[5], None) + self.assertEqual(view_discriminator[6], None) + self.assertEqual(view_discriminator[7], 'GET') + self.assertEqual(view_discriminator[8], 'name') + wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') + self.failUnless(wrapped) + + route_action = actions[1] + route_callable = route_action['callable'] + route_discriminator = route_action['discriminator'] + route_args = route_action['args'] + self.assertEqual(route_callable, connect_route) + self.assertEqual(len(route_discriminator), 2) + self.assertEqual(route_discriminator[0], 'route') + self.assertEqual(route_discriminator[1], 'name') + self.assertEqual(route_args, ('path', 'name', None)) + + def test_with_view_request_type_alias(self): + from zope.component import getSiteManager + from zope.interface import implementedBy + from repoze.bfg.zcml import connect_route + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRouteRequest + + context = DummyContext() + def view(context, request): + """ """ + self._callFUT(context, 'name', 'path', view=view, request_type="GET") + actions = context.actions + self.assertEqual(len(actions), 2) + + view_action = actions[0] + register = view_action['callable'] + register() + sm = getSiteManager() + request_factory = sm.getUtility(IRouteRequest, 'name') + request_type = implementedBy(request_factory) + view_discriminator = view_action['discriminator'] + self.assertEqual(len(view_discriminator), 9) + self.assertEqual(view_discriminator[0], 'view') + self.assertEqual(view_discriminator[1], None) + self.assertEqual(view_discriminator[2],'') + self.assertEqual(view_discriminator[3], request_type) + self.assertEqual(view_discriminator[4], IView) + self.assertEqual(view_discriminator[5], None) + self.assertEqual(view_discriminator[6], None) + self.assertEqual(view_discriminator[7], 'GET') + self.assertEqual(view_discriminator[8], 'name') + wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') + self.failUnless(wrapped) + + route_action = actions[1] + route_callable = route_action['callable'] + route_discriminator = route_action['discriminator'] + route_args = route_action['args'] + self.assertEqual(route_callable, connect_route) + self.assertEqual(len(route_discriminator), 2) + self.assertEqual(route_discriminator[0], 'route') + self.assertEqual(route_discriminator[1], 'name') + self.assertEqual(route_args, ('path', 'name', None)) + + def test_with_view_request_method(self): + from zope.component import getSiteManager + from zope.interface import implementedBy + from repoze.bfg.zcml import connect_route + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRouteRequest + + context = DummyContext() + def view(context, request): + """ """ + self._callFUT(context, 'name', 'path', view=view, + view_request_method="GET") + actions = context.actions + self.assertEqual(len(actions), 2) + + view_action = actions[0] + register = view_action['callable'] + register() + sm = getSiteManager() + request_factory = sm.getUtility(IRouteRequest, 'name') + request_type = implementedBy(request_factory) + view_discriminator = view_action['discriminator'] + self.assertEqual(len(view_discriminator), 9) + self.assertEqual(view_discriminator[0], 'view') + self.assertEqual(view_discriminator[1], None) + self.assertEqual(view_discriminator[2],'') + self.assertEqual(view_discriminator[3], request_type) + self.assertEqual(view_discriminator[4], IView) + self.assertEqual(view_discriminator[5], None) + self.assertEqual(view_discriminator[6], None) + self.assertEqual(view_discriminator[7], 'GET') + self.assertEqual(view_discriminator[8], 'name') + wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') + self.failUnless(wrapped) + + route_action = actions[1] + route_callable = route_action['callable'] + route_discriminator = route_action['discriminator'] + route_args = route_action['args'] + self.assertEqual(route_callable, connect_route) + self.assertEqual(len(route_discriminator), 2) + self.assertEqual(route_discriminator[0], 'route') + self.assertEqual(route_discriminator[1], 'name') + self.assertEqual(route_args, ('path', 'name', None)) + + def test_with_view_request_method_alias(self): + from zope.component import getSiteManager + from zope.interface import implementedBy + from repoze.bfg.zcml import connect_route + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRouteRequest + + context = DummyContext() + def view(context, request): + """ """ + self._callFUT(context, 'name', 'path', view=view, request_method="GET") + actions = context.actions + self.assertEqual(len(actions), 2) + + view_action = actions[0] + register = view_action['callable'] + register() + sm = getSiteManager() + request_factory = sm.getUtility(IRouteRequest, 'name') + request_type = implementedBy(request_factory) + view_discriminator = view_action['discriminator'] + self.assertEqual(len(view_discriminator), 9) + self.assertEqual(view_discriminator[0], 'view') + self.assertEqual(view_discriminator[1], None) + self.assertEqual(view_discriminator[2],'') + self.assertEqual(view_discriminator[3], request_type) + self.assertEqual(view_discriminator[4], IView) + self.assertEqual(view_discriminator[5], None) + self.assertEqual(view_discriminator[6], None) + self.assertEqual(view_discriminator[7], 'GET') + self.assertEqual(view_discriminator[8], 'name') + wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') + self.failUnless(wrapped) + + route_action = actions[1] + route_callable = route_action['callable'] + route_discriminator = route_action['discriminator'] + route_args = route_action['args'] + self.assertEqual(route_callable, connect_route) + self.assertEqual(len(route_discriminator), 2) + self.assertEqual(route_discriminator[0], 'route') + self.assertEqual(route_discriminator[1], 'name') + self.assertEqual(route_args, ('path', 'name', None)) + + def test_with_view_containment(self): + from zope.component import getSiteManager + from zope.interface import implementedBy + from repoze.bfg.zcml import connect_route + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRouteRequest + + context = DummyContext() + def view(context, request): + """ """ + self._callFUT(context, 'name', 'path', view=view, view_containment=True) + actions = context.actions + self.assertEqual(len(actions), 2) + + view_action = actions[0] + register = view_action['callable'] + register() + sm = getSiteManager() + request_factory = sm.getUtility(IRouteRequest, 'name') + request_type = implementedBy(request_factory) + view_discriminator = view_action['discriminator'] + self.assertEqual(len(view_discriminator), 9) + self.assertEqual(view_discriminator[0], 'view') + self.assertEqual(view_discriminator[1], None) + self.assertEqual(view_discriminator[2],'') + self.assertEqual(view_discriminator[3], request_type) + self.assertEqual(view_discriminator[4], IView) + self.assertEqual(view_discriminator[5], True) + self.assertEqual(view_discriminator[6], None) + self.assertEqual(view_discriminator[7], None) + self.assertEqual(view_discriminator[8], 'name') + wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') + self.failUnless(wrapped) + + route_action = actions[1] route_callable = route_action['callable'] route_discriminator = route_action['discriminator'] route_args = route_action['args'] self.assertEqual(route_callable, connect_route) - self.assertEqual(len(route_discriminator), 4) + self.assertEqual(len(route_discriminator), 2) + self.assertEqual(route_discriminator[0], 'route') + self.assertEqual(route_discriminator[1], 'name') + self.assertEqual(route_args, ('path', 'name', None)) + + def test_with_view_containment_alias(self): + from zope.component import getSiteManager + from zope.interface import implementedBy + from repoze.bfg.zcml import connect_route + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRouteRequest + + context = DummyContext() + def view(context, request): + """ """ + self._callFUT(context, 'name', 'path', view=view, containment=True) + actions = context.actions + self.assertEqual(len(actions), 2) + + view_action = actions[0] + register = view_action['callable'] + register() + sm = getSiteManager() + request_factory = sm.getUtility(IRouteRequest, 'name') + request_type = implementedBy(request_factory) + view_discriminator = view_action['discriminator'] + self.assertEqual(len(view_discriminator), 9) + self.assertEqual(view_discriminator[0], 'view') + self.assertEqual(view_discriminator[1], None) + self.assertEqual(view_discriminator[2],'') + self.assertEqual(view_discriminator[3], request_type) + self.assertEqual(view_discriminator[4], IView) + self.assertEqual(view_discriminator[5], True) + self.assertEqual(view_discriminator[6], None) + self.assertEqual(view_discriminator[7], None) + self.assertEqual(view_discriminator[8], 'name') + wrapped = sm.adapters.lookup((IDummy, request_type), IView, name='') + self.failUnless(wrapped) + + route_action = actions[1] + route_callable = route_action['callable'] + route_discriminator = route_action['discriminator'] + route_args = route_action['args'] + self.assertEqual(route_callable, connect_route) + self.assertEqual(len(route_discriminator), 2) self.assertEqual(route_discriminator[0], 'route') self.assertEqual(route_discriminator[1], 'name') - self.assertEqual(route_discriminator[2], None) - self.assertEqual(route_discriminator[3], 'GET') self.assertEqual(route_args, ('path', 'name', None)) class TestStaticDirective(unittest.TestCase): @@ -1049,65 +1697,87 @@ class TestStaticDirective(unittest.TestCase): return static(*arg, **kw) def test_absolute(self): + from paste.urlparser import StaticURLParser + from zope.interface import implementedBy + from zope.component import getSiteManager + from repoze.bfg.zcml import connect_route + from repoze.bfg.zcml import StaticRootFactory + from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRouteRequest import os here = os.path.dirname(__file__) static_path = os.path.join(here, 'fixtures', 'static') - from repoze.bfg.zcml import handler - from repoze.bfg.zcml import connect_route - from repoze.bfg.interfaces import IView context = DummyContext() self._callFUT(context, 'name', static_path) actions = context.actions self.assertEqual(len(actions), 2) action = actions[0] - callable = action['callable'] discriminator = action['discriminator'] - args = action['args'] - self.assertEqual(callable, handler) - self.assertEqual(discriminator[:3], ('view', None, '')) + self.assertEqual(discriminator[:3], ('view', StaticRootFactory, '')) self.assertEqual(discriminator[4], IView) - self.assertEqual(args[1].app.directory, static_path) + sm = getSiteManager() + register = action['callable'] + register() + sm = getSiteManager() + iface = implementedBy(StaticRootFactory) + request_factory = sm.getUtility(IRouteRequest, 'name') + request_type = implementedBy(request_factory) + wrapped = sm.adapters.lookup((iface, request_type), IView, name='') + request = DummyRequest() + self.assertEqual(wrapped(None, request).__class__, StaticURLParser) action = actions[1] callable = action['callable'] discriminator = action['discriminator'] args = action['args'] self.assertEqual(callable, connect_route) - self.assertEqual(discriminator, ('route', 'name', None, None)) + self.assertEqual(discriminator, ('route', 'name')) self.assertEqual(args[0], 'name*subpath') def test_package_relative(self): - from repoze.bfg.zcml import handler + from repoze.bfg.static import PackageURLParser + from zope.component import getSiteManager + from zope.interface import implementedBy from repoze.bfg.zcml import connect_route + from repoze.bfg.zcml import StaticRootFactory from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRouteRequest context = DummyContext() self._callFUT(context, 'name', 'repoze.bfg.tests:fixtures/static') actions = context.actions self.assertEqual(len(actions), 2) action = actions[0] - callable = action['callable'] discriminator = action['discriminator'] - args = action['args'] - self.assertEqual(callable, handler) - self.assertEqual(discriminator[:3], ('view', None, '')) + self.assertEqual(discriminator[:3], ('view', StaticRootFactory, '')) self.assertEqual(discriminator[4], IView) - self.assertEqual(args[1].app.package_name, 'repoze.bfg.tests') - self.assertEqual(args[1].app.resource_name, 'fixtures/static') + register = action['callable'] + register() + sm = getSiteManager() + iface = implementedBy(StaticRootFactory) + request_factory = sm.getUtility(IRouteRequest, 'name') + request_type = implementedBy(request_factory) + view = sm.adapters.lookup((iface, request_type), IView, name='') + request = DummyRequest() + self.assertEqual(view(None, request).__class__, PackageURLParser) action = actions[1] callable = action['callable'] discriminator = action['discriminator'] args = action['args'] self.assertEqual(callable, connect_route) - self.assertEqual(discriminator, ('route', 'name', None, None)) + self.assertEqual(discriminator, ('route', 'name')) self.assertEqual(args[0], 'name*subpath') def test_here_relative(self): - from repoze.bfg.zcml import handler + from repoze.bfg.static import PackageURLParser + from zope.component import getSiteManager + from zope.interface import implementedBy from repoze.bfg.zcml import connect_route + from repoze.bfg.zcml import StaticRootFactory from repoze.bfg.interfaces import IView + from repoze.bfg.interfaces import IRouteRequest import repoze.bfg.tests context = DummyContext(repoze.bfg.tests) self._callFUT(context, 'name', 'fixtures/static') @@ -1115,21 +1785,25 @@ class TestStaticDirective(unittest.TestCase): self.assertEqual(len(actions), 2) action = actions[0] - callable = action['callable'] discriminator = action['discriminator'] - args = action['args'] - self.assertEqual(callable, handler) - self.assertEqual(discriminator[:3], ('view', None, '')) + self.assertEqual(discriminator[:3], ('view', StaticRootFactory, '')) self.assertEqual(discriminator[4], IView) - self.assertEqual(args[1].app.package_name, 'repoze.bfg.tests') - self.assertEqual(args[1].app.resource_name, 'fixtures/static') + register = action['callable'] + register() + sm = getSiteManager() + iface = implementedBy(StaticRootFactory) + request_factory = sm.getUtility(IRouteRequest, 'name') + request_type = implementedBy(request_factory) + wrapped = sm.adapters.lookup((iface, request_type), IView, name='') + request = DummyRequest() + self.assertEqual(wrapped(None, request).__class__, PackageURLParser) action = actions[1] callable = action['callable'] discriminator = action['discriminator'] args = action['args'] self.assertEqual(callable, connect_route) - self.assertEqual(discriminator, ('route', 'name', None, None)) + self.assertEqual(discriminator, ('route', 'name')) self.assertEqual(args[0], 'name*subpath') class TestResourceDirective(unittest.TestCase): @@ -1372,22 +2046,36 @@ class TestBFGViewFunctionGrokker(unittest.TestCase): return self._getTargetClass()(*arg, **kw) def test_grok_is_bfg_view(self): + from zope.component import getSiteManager from repoze.bfg.interfaces import IRequest + from repoze.bfg.interfaces import IView from zope.interface import Interface grokker = self._makeOne() class obj: - pass + def __init__(self, context, request): + pass + def __call__(self): + return 'OK' obj.__is_bfg_view__ = True obj.__permission__ = 'foo' obj.__for__ = Interface obj.__view_name__ = 'foo.html' obj.__request_type__ = IRequest obj.__route_name__ = None + obj.__request_method__ = None + obj.__request_param__ = None + obj.__containment__ = None context = DummyContext() result = grokker.grok('name', obj, context=context) self.assertEqual(result, True) actions = context.actions - self.assertEqual(len(actions), 2) + self.assertEqual(len(actions), 1) + register = actions[0]['callable'] + register() + sm = getSiteManager() + wrapped = sm.adapters.lookup((Interface, IRequest), IView, + name='foo.html') + self.assertEqual(wrapped(None, None), 'OK') def test_grok_is_not_bfg_view(self): grokker = self._makeOne() @@ -1436,152 +2124,17 @@ class TestExcludeFunction(unittest.TestCase): self.assertEqual(self._callFUT('.foo'), True) self.assertEqual(self._callFUT('foo'), False) -class TestRequestOnly(unittest.TestCase): - def _callFUT(self, arg): - from repoze.bfg.zcml import requestonly - return requestonly(arg) - - def test_newstyle_class_no_init(self): - class foo(object): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_newstyle_class_init_toomanyargs(self): - class foo(object): - def __init__(self, context, request): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_newstyle_class_init_onearg_named_request(self): - class foo(object): - def __init__(self, request): - """ """ - self.assertTrue(self._callFUT(foo)) - - def test_newstyle_class_init_onearg_named_somethingelse(self): - class foo(object): - def __init__(self, req): - """ """ - self.assertTrue(self._callFUT(foo)) - - def test_newstyle_class_init_defaultargs_firstname_not_request(self): - class foo(object): - def __init__(self, context, request=None): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_newstyle_class_init_defaultargs_firstname_request(self): - class foo(object): - def __init__(self, request, foo=1, bar=2): - """ """ - self.assertTrue(self._callFUT(foo)) - - def test_newstyle_class_init_noargs(self): - class foo(object): - def __init__(): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_oldstyle_class_no_init(self): - class foo: - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_oldstyle_class_init_toomanyargs(self): - class foo: - def __init__(self, context, request): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_oldstyle_class_init_onearg_named_request(self): - class foo: - def __init__(self, request): - """ """ - self.assertTrue(self._callFUT(foo)) - - def test_oldstyle_class_init_onearg_named_somethingelse(self): - class foo: - def __init__(self, req): - """ """ - self.assertTrue(self._callFUT(foo)) - - def test_oldstyle_class_init_defaultargs_firstname_not_request(self): - class foo: - def __init__(self, context, request=None): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_oldstyle_class_init_defaultargs_firstname_request(self): - class foo: - def __init__(self, request, foo=1, bar=2): - """ """ - self.assertTrue(self._callFUT(foo), True) - - def test_oldstyle_class_init_noargs(self): - class foo: - def __init__(): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_function_toomanyargs(self): - def foo(context, request): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_function_onearg_named_request(self): - def foo(request): - """ """ - self.assertTrue(self._callFUT(foo)) - - def test_function_onearg_named_somethingelse(self): - def foo(req): - """ """ - self.assertTrue(self._callFUT(foo)) - - def test_function_defaultargs_firstname_not_request(self): - def foo(context, request=None): - """ """ - self.assertFalse(self._callFUT(foo)) - - def test_function_defaultargs_firstname_request(self): - def foo(request, foo=1, bar=2): - """ """ - self.assertTrue(self._callFUT(foo), True) - - def test_instance_toomanyargs(self): - class Foo: - def __call__(self, context, request): - """ """ - foo = Foo() - self.assertFalse(self._callFUT(foo)) - - def test_instance_defaultargs_onearg_named_request(self): - class Foo: - def __call__(self, request): - """ """ - foo = Foo() - self.assertTrue(self._callFUT(foo)) - - def test_instance_defaultargs_onearg_named_somethingelse(self): - class Foo: - def __call__(self, req): - """ """ - foo = Foo() - self.assertTrue(self._callFUT(foo)) - - def test_instance_defaultargs_firstname_not_request(self): - class Foo: - def __call__(self, context, request=None): - """ """ - foo = Foo() - self.assertFalse(self._callFUT(foo)) +class TestAll(unittest.TestCase): + def test_it(self): + from repoze.bfg.zcml import all + self.assertEqual(all([True, True]), True) + self.assertEqual(all([False, False]), False) + self.assertEqual(all([False, True]), False) - def test_instance_defaultargs_firstname_request(self): - class Foo: - def __call__(self, request, foo=1, bar=2): - """ """ - foo = Foo() - self.assertTrue(self._callFUT(foo), True) +class TestStaticRootFactory(unittest.TestCase): + def test_it(self): + from repoze.bfg.zcml import StaticRootFactory + StaticRootFactory({}) # it just needs construction class DummyModule: __path__ = "foo" @@ -1612,7 +2165,7 @@ class DummyContext: self.info = None self.resolved = resolved - def action(self, discriminator, callable, args): + def action(self, discriminator, callable=None, args=(), kw={}, order=0): self.actions.append( {'discriminator':discriminator, 'callable':callable, @@ -1639,9 +2192,6 @@ from zope.interface import Interface class IDummy(Interface): pass -class DummySecurityPolicy: - pass - class DummyLogger: def __init__(self): self.messages = [] @@ -1651,9 +2201,18 @@ class DummyLogger: debug = info class DummyRequest: + subpath = () + def __init__(self, environ=None): + if environ is None: + environ = {} + self.environ = environ + def get_response(self, app): return app - + + def copy(self): + return self + class DummyOverrides: def __init__(self, package): self.package = package @@ -1666,3 +2225,17 @@ class DummyPackage: def __init__(self, name): self.__name__ = name +class DummySettings(dict): + def __getattr__(self, name): + return self[name] + +class DummySecurityPolicy: + def __init__(self, permitted=True): + self.permitted = permitted + + def effective_principals(self, request): + return [] + + def permits(self, context, principals, permission): + return self.permitted + diff --git a/repoze/bfg/view.py b/repoze/bfg/view.py index 4fea8bf84..50bd7b86b 100644 --- a/repoze/bfg/view.py +++ b/repoze/bfg/view.py @@ -17,16 +17,20 @@ from webob import Response from paste.urlparser import StaticURLParser -from zope.component import queryMultiAdapter from zope.component import queryUtility +from zope.component import providedBy +from zope.component import getSiteManager + +from zope.interface import implements + from zope.deprecation import deprecated -from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IResponseFactory -from repoze.bfg.path import caller_path +from repoze.bfg.interfaces import IView +from repoze.bfg.interfaces import IMultiView + from repoze.bfg.path import caller_package -from repoze.bfg.security import view_execution_permitted -from repoze.bfg.security import Unauthorized + from repoze.bfg.static import PackageURLParser deprecated('view_execution_permitted', @@ -50,19 +54,21 @@ def render_view_to_response(context, request, name='', secure=True): ``repoze.bfg.security.Unauthorized`` exception will be raised; its ``args`` attribute explains why the view access was disallowed. If ``secure`` is ``False``, no permission checking is done.""" - if secure: - permitted = view_execution_permitted(context, request, name) - if not permitted: - raise Unauthorized(permitted) - - # It's no use trying to distinguish below whether response is None - # because a) we were returned a default or b) because the view - # function returned None: the zope.component/zope.interface - # machinery doesn't distinguish a None returned from the view from - # a sentinel None returned during queryMultiAdapter (even if we - # pass a non-None default). - - return queryMultiAdapter((context, request), IView, name=name) + provides = map(providedBy, (context, request)) + sm = getSiteManager() + view = sm.adapters.lookup(provides, IView, name=name) + if view is None: + return None + + if not secure: + # the view will have a __permissive_view__ attribute if it's + # secured; otherwise it won't. + view = getattr(view, '__call_permissive__', view) + + # if this view is secured, it will raise an Unauthorized + # appropriately if the executing user does not have the proper + # permission + return view(context, request) def render_view_to_iterable(context, request, name='', secure=True): """ Render the view named ``name`` against the specified @@ -211,23 +217,53 @@ class bfg_view(object): route_name='site1' /> - If ``name`` is not supplied, the empty string is used (implying - the default view). - - If ``request_type`` is not supplied, the interface - ``repoze.bfg.interfaces.IRequest`` is used. + The following arguments are supported: ``for_``, ``permission``, + ``name``, ``request_type``, ``route_name``, ``request_method``, + ``request_param``, and ``containment``. If ``for_`` is not supplied, the interface - ``zope.interface.Interface`` (implying *all* interfaces) is used. + ``zope.interface.Interface`` (matching any context) is used. If ``permission`` is not supplied, no permission is registered for this view (it's accessible by any caller). + If ``name`` is not supplied, the empty string is used (implying + the default view name). + + If ``request_type`` is not supplied, the interface + ``repoze.bfg.interfaces.IRequest`` is used, implying the standard + request interface type. + If ``route_name`` is not supplied, the view declaration is considered to be made against a URL that doesn't match any defined :term:`route`. The use of a ``route_name`` is an advanced feature, useful only if you're using :term:`url dispatch`. + If ``request_method`` is not supplied, this view will match a + request with any HTTP ``REQUEST_METHOD`` + (GET/POST/PUT/HEAD/DELETE). If this parameter *is* supplied, it + must be a string naming an HTTP ``REQUEST_METHOD``, indicating + that this view will only match when the current request has a + ``REQUEST_METHOD`` that matches this value. + + If ``request_param`` is not supplied, this view will be called + when a request with any (or no) request GET or POST parameters is + encountered. If the value is present, it must be a string. If + the value supplied to the parameter has no ``=`` sign in it, it + implies that the key must exist in the ``request.params`` + dictionary for this view to'match' the current request. If the value + supplied to the parameter has a ``=`` sign in it, e.g. + ``request_params="foo=123"``, then the key (``foo``) must both exist + in the ``request.params`` dictionary, and the value must match the + right hand side of the expression (``123``) for the view to "match" the + current request. + + If ``containment`` is not supplied, this view will be called when + the context of the request has any location lineage. If + ``containment`` *is* supplied, it must be a class or :term:`interface`, + denoting that the view 'matches' the current request only if any graph + lineage node possesses this class or interface. + Any individual or all parameters can be omitted. The simplest bfg_view declaration then becomes:: @@ -239,7 +275,9 @@ class bfg_view(object): ``my_view``, registered for models with the ``zope.interface.Interface`` interface, using no permission, registered against requests which implement the default IRequest - interface when no urldispatch route matches. + interface when no urldispatch route matches, with any + REQUEST_METHOD, any set of request.params values, in any lineage + containment. The ``bfg_view`` decorator can also be used as a class decorator in Python 2.6 and better (Python 2.5 and below do not support @@ -284,35 +322,28 @@ class bfg_view(object): <scan package="."/> """ def __init__(self, name='', request_type=None, for_=None, permission=None, - route_name=None): + route_name=None, request_method=None, request_param=None, + containment=None): self.name = name self.request_type = request_type self.for_ = for_ self.permission = permission self.route_name = route_name + self.request_method = request_method + self.request_param = request_param + self.containment = containment def __call__(self, wrapped): - _bfg_view = wrapped - if inspect.isclass(_bfg_view): - # If the object we're decorating is a class, turn it into - # a function that operates like a Zope view (when it's - # invoked, construct an instance using 'context' and - # 'request' as position arguments, then immediately invoke - # the __call__ method of the instance with no arguments; - # __call__ should return an IResponse). - def _bfg_class_view(context, request): - inst = wrapped(context, request) - return inst() - _bfg_class_view.__module__ = wrapped.__module__ - _bfg_class_view.__name__ = wrapped.__name__ - _bfg_class_view.__doc__ = wrapped.__doc__ - _bfg_view = _bfg_class_view + _bfg_view = map_view(wrapped) _bfg_view.__is_bfg_view__ = True _bfg_view.__permission__ = self.permission _bfg_view.__for__ = self.for_ _bfg_view.__view_name__ = self.name _bfg_view.__request_type__ = self.request_type _bfg_view.__route_name__ = self.route_name + _bfg_view.__request_method__ = self.request_method + _bfg_view.__request_param__ = self.request_param + _bfg_view.__containment__ = self.containment return _bfg_view def default_view(context, request, status): @@ -342,3 +373,141 @@ def default_forbidden_view(context, request): def default_notfound_view(context, request): return default_view(context, request, '404 Not Found') +class NotFound(Exception): + pass + +class MultiView(object): + implements(IMultiView) + + def __init__(self, name): + self.name = name + self.views = [] + + def add(self, view, score): + self.views.append((score, view)) + self.views.sort() + + def match(self, context, request): + for score, view in self.views: + if not hasattr(view, '__predicated__'): + return view + if view.__predicated__(context, request): + return view + raise NotFound(self.name) + + def __permitted__(self, context, request): + view = self.match(context, request) + if hasattr(view, '__permitted__'): + return view.__permitted__(context, request) + return True + + def __call_permissive__(self, context, request): + view = self.match(context, request) + view = getattr(view, '__call_permissive__', view) + return view(context, request) + + def __call__(self, context, request): + for score, view in self.views: + try: + return view(context, request) + except NotFound: + continue + raise NotFound(self.name) + +def map_view(view): + wrapped_view = view + + if inspect.isclass(view): + # If the object we've located is a class, turn it into a + # function that operates like a Zope view (when it's invoked, + # construct an instance using 'context' and 'request' as + # position arguments, then immediately invoke the __call__ + # method of the instance with no arguments; __call__ should + # return an IResponse). + if requestonly(view): + # its __init__ accepts only a single request argument, + # instead of both context and request + def _bfg_class_requestonly_view(context, request): + inst = view(request) + return inst() + wrapped_view = _bfg_class_requestonly_view + else: + # its __init__ accepts both context and request + def _bfg_class_view(context, request): + inst = view(context, request) + return inst() + wrapped_view = _bfg_class_view + + elif requestonly(view): + # its __call__ accepts only a single request argument, + # instead of both context and request + def _bfg_requestonly_view(context, request): + return view(request) + wrapped_view = _bfg_requestonly_view + + decorate_view(wrapped_view, view) + return wrapped_view + +def requestonly(class_or_callable): + """ Return true of the class or callable accepts only a request argument, + as opposed to something that accepts context, request """ + if inspect.isfunction(class_or_callable): + fn = class_or_callable + elif inspect.isclass(class_or_callable): + try: + fn = class_or_callable.__init__ + except AttributeError: + return False + else: + try: + fn = class_or_callable.__call__ + except AttributeError: + return False + + try: + argspec = inspect.getargspec(fn) + except TypeError: + return False + + args = argspec[0] + defaults = argspec[3] + + if hasattr(fn, 'im_func'): + # it's an instance method + if not args: + return False + args = args[1:] + if not args: + return False + + if len(args) == 1: + return True + + elif args[0] == 'request': + if len(args) - len(defaults) == 1: + return True + + return False + +def decorate_view(wrapped_view, original_view): + if wrapped_view is not original_view: + wrapped_view.__module__ = original_view.__module__ + wrapped_view.__doc__ = original_view.__doc__ + try: + wrapped_view.__name__ = original_view.__name__ + except AttributeError: + wrapped_view.__name__ = repr(original_view) + try: + wrapped_view.__permitted__ = original_view.__permitted__ + except AttributeError: + pass + try: + wrapped_view.__call_permissive__ = original_view.__call_permissive__ + except AttributeError: + pass + try: + wrapped_view.__predicated__ = original_view.__predicated__ + except AttributeError: + pass + return True + return False diff --git a/repoze/bfg/zcml.py b/repoze/bfg/zcml.py index 5a41babd1..fb4e8d720 100644 --- a/repoze/bfg/zcml.py +++ b/repoze/bfg/zcml.py @@ -1,5 +1,5 @@ import os -import inspect +import sys import types from zope.configuration import xmlconfig @@ -14,6 +14,8 @@ from zope.configuration.exceptions import ConfigurationError from zope.configuration.fields import GlobalObject from zope.interface import Interface +from zope.interface.interfaces import IInterface +from zope.interface import implementedBy from zope.schema import TextLine from zope.schema import Bool @@ -32,30 +34,45 @@ from repoze.bfg.interfaces import IForbiddenView from repoze.bfg.interfaces import IAuthenticationPolicy from repoze.bfg.interfaces import IAuthorizationPolicy from repoze.bfg.interfaces import ISecurityPolicy +from repoze.bfg.interfaces import ISecuredView +from repoze.bfg.interfaces import IMultiView from repoze.bfg.interfaces import IView from repoze.bfg.interfaces import IUnauthorizedAppFactory from repoze.bfg.interfaces import ILogger -from repoze.bfg.interfaces import IRequestFactories from repoze.bfg.interfaces import IPackageOverrides +from repoze.bfg.interfaces import IRequest +from repoze.bfg.interfaces import IRouteRequest from repoze.bfg.path import package_name from repoze.bfg.resource import PackageOverrides -from repoze.bfg.request import DEFAULT_REQUEST_FACTORIES -from repoze.bfg.request import named_request_factories +from repoze.bfg.request import create_route_request_factory -from repoze.bfg.security import ViewPermissionFactory +from repoze.bfg.security import Unauthorized from repoze.bfg.secpols import registerBBBAuthn +from repoze.bfg.settings import get_settings + +from repoze.bfg.traversal import find_interface from repoze.bfg.view import static as static_view +from repoze.bfg.view import NotFound +from repoze.bfg.view import MultiView +from repoze.bfg.view import map_view +from repoze.bfg.view import decorate_view + import martian -def handler(methodName, *args, **kwargs): - method = getattr(getSiteManager(), methodName) - method(*args, **kwargs) +try: + all = all +except NameError: # pragma: no cover + def all(iterable): + for element in iterable: + if not element: + return False + return True def view( _context, @@ -65,56 +82,154 @@ def view( name="", request_type=None, route_name=None, + request_method=None, + request_param=None, + containment=None, cacheable=True, # not used, here for b/w compat < 0.8 ): if not view: raise ConfigurationError('"view" attribute was not specified') - if route_name is None: - request_factories = DEFAULT_REQUEST_FACTORIES - else: - request_factories = queryUtility(IRequestFactories, name=route_name) - if request_factories is None: - raise ConfigurationError( - 'Unknown route_name "%s". <route> definitions must be ordered ' - 'before the view definition which mentions the route\'s name ' - 'within ZCML (or before the "scan" directive is invoked ' - 'within a bfg_view decorator).' % route_name) - - if request_type in request_factories: - request_type = request_factories[request_type]['interface'] - else: - request_type = _context.resolve(request_type) + sm = getSiteManager() + + if request_type in ('GET', 'HEAD', 'PUT', 'POST', 'DELETE'): + # b/w compat for 1.0 + request_method = request_type + request_type = None - derived_view = derive_view(view) + if request_type is None: + if route_name is None: + request_type = IRequest + else: + request_type = queryUtility(IRouteRequest, name=route_name) + if request_type is None: + factory = create_route_request_factory(route_name) + request_type = implementedBy(factory) + sm.registerUtility(factory, IRouteRequest, name=route_name) - if permission: - pfactory = ViewPermissionFactory(permission) - _context.action( - discriminator = ('permission', for_, name, request_type, - IViewPermission), - callable = handler, - args = ('registerAdapter', - pfactory, (for_, request_type), IViewPermission, name, - _context.info), - ) + if isinstance(request_type, basestring): + request_type = _context.resolve(request_type) + predicates = [] + weight = sys.maxint + + # Predicates are added to the predicate list in (presumed) + # computation expense order. All predicates associated with a + # view must evaluate true for the view to "match" a request. + # Elsewhere in the code, we evaluate them using a generator + # expression. The fastest predicate should be evaluated first, + # then the next fastest, and so on, as if one returns false, the + # remainder of the predicates won't need to be evaluated. + + # Each predicate is associated with a weight value. The weight + # symbolizes the relative potential "importance" of the predicate + # to all other predicates. A larger weight indicates greater + # importance. These weights are subtracted from an aggregate + # 'weight' variable. The aggregate weight is then divided by the + # length of the predicate list to compute a "score" for this view. + # The score represents the ordering in which a "multiview" ( a + # collection of views that share the same context/request/name + # triad but differ in other ways via predicates) will attempt to + # call its set of views. Views with lower scores will be tried + # first. The intent is to a) ensure that views with more + # predicates are always evaluated before views with fewer + # predicates and b) to ensure a stable call ordering of views that + # share the same number of predicates. + + # Views which do not have any predicates get a score of + # "sys.maxint", meaning that they will be tried very last. + + if request_method is not None: + def request_method_predicate(context, request): + return request.method == request_method + weight = weight - 10 + predicates.append(request_method_predicate) + + if request_param is not None: + request_param_val = None + if '=' in request_param: + request_param, request_param_val = request_param.split('=', 1) + def request_param_predicate(context, request): + if request_param_val is None: + return request_param in request.params + return request.params.get(request_param) == request_param_val + weight = weight - 20 + predicates.append(request_param_predicate) + + if containment is not None: + def containment_predicate(context, request): + return find_interface(context, containment) is not None + weight = weight - 30 + predicates.append(containment_predicate) + + if predicates: + score = float(weight) / len(predicates) + else: + score = sys.maxint + + def register(): + derived_view = derive_view(view, permission, predicates) + r_for_ = for_ + r_request_type = request_type + if r_for_ is None: + r_for_ = Interface + if not IInterface.providedBy(r_for_): + r_for_ = implementedBy(r_for_) + if not IInterface.providedBy(r_request_type): + r_request_type = implementedBy(r_request_type) + old_view = sm.adapters.lookup((r_for_, r_request_type), IView,name=name) + if old_view is None: + if hasattr(derived_view, '__call_permissive__'): + sm.registerAdapter(derived_view, (for_, request_type), + ISecuredView, name, _context.info) + if hasattr(derived_view, '__permitted__'): + # bw compat + sm.registerAdapter(derived_view.__permitted__, + (for_, request_type), IViewPermission, + name, _context.info) + else: + sm.registerAdapter(derived_view, (for_, request_type), + IView, name, _context.info) + else: + # XXX we could try to be more efficient here and register + # a non-secured view for a multiview if none of the + # multiview's consituent views have a permission + # associated with them, but this code is getting pretty + # rough already + if IMultiView.providedBy(old_view): + multiview = old_view + else: + multiview = MultiView(name) + multiview.add(old_view, sys.maxint) + multiview.add(derived_view, score) + for i in (IView, ISecuredView): + # unregister any existing views + sm.adapters.unregister((r_for_, r_request_type), i, name=name) + sm.registerAdapter(multiview, (for_, request_type), IMultiView, + name, _context.info) + # b/w compat + sm.registerAdapter(multiview.__permitted__, + (for_, request_type), IViewPermission, + name, _context.info) _context.action( - discriminator = ('view', for_, name, request_type, IView), - callable = handler, - args = ('registerAdapter', - derived_view, (for_, request_type), IView, name, _context.info), + discriminator = ('view', for_, name, request_type, IView, containment, + request_param, request_method, route_name), + callable = register, + args = (), ) _view = view # for directives that take a view arg def view_utility(_context, view, iface): - derived_view = derive_view(view) + def register(): + derived_view = derive_view(view) + sm = getSiteManager() + sm.registerUtility(derived_view, iface, '', _context.info) + _context.action( - discriminator = ('notfound_view',), - callable = handler, - args = ('registerUtility', derived_view, iface, '', _context.info), + discriminator = iface, + callable = register, ) def notfound(_context, view): @@ -123,46 +238,83 @@ def notfound(_context, view): def forbidden(_context, view): view_utility(_context, view, IForbiddenView) -def derive_view(view): - derived_view = view - if inspect.isclass(view): - # If the object we've located is a class, turn it into a - # function that operates like a Zope view (when it's invoked, - # construct an instance using 'context' and 'request' as - # position arguments, then immediately invoke the __call__ - # method of the instance with no arguments; __call__ should - # return an IResponse). - if requestonly(view): - # its __init__ accepts only a single request argument, - # instead of both context and request - def _bfg_class_requestonly_view(context, request): - inst = view(request) - return inst() - derived_view = _bfg_class_requestonly_view - else: - # its __init__ accepts both context and request - def _bfg_class_view(context, request): - inst = view(context, request) - return inst() - derived_view = _bfg_class_view - - elif requestonly(view): - # its __call__ accepts only a single request argument, - # instead of both context and request - def _bfg_requestonly_view(context, request): - return view(request) - derived_view = _bfg_requestonly_view - - if derived_view is not view: - derived_view.__module__ = view.__module__ - derived_view.__doc__ = view.__doc__ - try: - derived_view.__name__ = view.__name__ - except AttributeError: - derived_view.__name__ = repr(view) - +def derive_view(original_view, permission=None, predicates=()): + mapped_view = map_view(original_view) + secured_view = secure_view(mapped_view, permission) + debug_view = authdebug_view(secured_view, permission) + derived_view = predicate_wrap(debug_view, predicates) return derived_view - + +def predicate_wrap(view, predicates): + if not predicates: + return view + def _wrapped(context, request): + if all((predicate(context, request) for predicate in predicates)): + return view(context, request) + raise NotFound('predicate mismatch for view %s' % view) + def checker(context, request): + return all((predicate(context, request) for predicate in predicates)) + _wrapped.__predicated__ = checker + decorate_view(_wrapped, view) + return _wrapped + +def secure_view(view, permission): + wrapped_view = view + authn_policy = queryUtility(IAuthenticationPolicy) + authz_policy = queryUtility(IAuthorizationPolicy) + if authn_policy and authz_policy and (permission is not None): + def _secured_view(context, request): + principals = authn_policy.effective_principals(request) + if authz_policy.permits(context, principals, permission): + return view(context, request) + msg = getattr(request, 'authdebug_message', + 'Unauthorized: %s failed permission check' % view) + raise Unauthorized(msg) + _secured_view.__call_permissive__ = view + def _permitted(context, request): + principals = authn_policy.effective_principals(request) + return authz_policy.permits(context, principals, permission) + _secured_view.__permitted__ = _permitted + wrapped_view = _secured_view + decorate_view(wrapped_view, view) + + return wrapped_view + +def authdebug_view(view, permission): + wrapped_view = view + authn_policy = queryUtility(IAuthenticationPolicy) + authz_policy = queryUtility(IAuthorizationPolicy) + settings = get_settings() + debug_authorization = getattr(settings, 'debug_authorization', False) + if debug_authorization: + def _authdebug_view(context, request): + view_name = getattr(request, 'view_name', None) + + if authn_policy and authz_policy: + if permission is None: + msg = 'Allowed (no permission registered)' + else: + principals = authn_policy.effective_principals(request) + msg = str(authz_policy.permits(context, principals, + permission)) + else: + msg = 'Allowed (no authorization policy in use)' + + view_name = getattr(request, 'view_name', None) + url = getattr(request, 'url', None) + msg = ('debug_authorization of url %s (view name %r against ' + 'context %r): %s' % (url, view_name, context, msg)) + logger = getUtility(ILogger, 'repoze.bfg.debug') + logger and logger.debug(msg) + if request is not None: + request.authdebug_message = msg + return view(context, request) + + wrapped_view = _authdebug_view + decorate_view(wrapped_view, view) + + return wrapped_view + def scan(_context, package, martian=martian): # martian overrideable only for unit tests module_grokker = martian.ModuleGrokker() @@ -240,12 +392,11 @@ def repozewho1authenticationpolicy(_context, identifier_name='auth_tkt', callback=None): policy = RepozeWho1AuthenticationPolicy(identifier_name=identifier_name, callback=callback) - _context.action( - discriminator = 'authentication_policy', - callable = handler, - args = ('registerUtility', policy, IAuthenticationPolicy, '', - _context.info), - ) + # authentication policies must be registered eagerly so they can + # be found by the view registration machinery + sm = getSiteManager() + sm.registerUtility(policy, IAuthenticationPolicy) + _context.action(discriminator=IAuthenticationPolicy) class IRemoteUserAuthenticationPolicyDirective(Interface): environ_key = TextLine(title=u'environ_key', required=False, @@ -256,12 +407,11 @@ def remoteuserauthenticationpolicy(_context, environ_key='REMOTE_USER', callback=None): policy = RemoteUserAuthenticationPolicy(environ_key=environ_key, callback=callback) - _context.action( - discriminator = 'authentication_policy', - callable = handler, - args = ('registerUtility', policy, IAuthenticationPolicy, '', - _context.info), - ) + # authentication policies must be registered eagerly so they can + # be found by the view registration machinery + sm = getSiteManager() + sm.registerUtility(policy, IAuthenticationPolicy) + _context.action(discriminator=IAuthenticationPolicy) class IAuthTktAuthenticationPolicyDirective(Interface): secret = TextLine(title=u'secret', required=True) @@ -291,49 +441,91 @@ def authtktauthenticationpolicy(_context, reissue_time = reissue_time) except ValueError, why: raise ConfigurationError(str(why)) - _context.action( - discriminator = 'authentication_policy', - callable = handler, - args = ('registerUtility', policy, IAuthenticationPolicy, '', - _context.info), - ) + # authentication policies must be registered eagerly so they can + # be found by the view registration machinery + sm = getSiteManager() + sm.registerUtility(policy, IAuthenticationPolicy) + _context.action(discriminator=IAuthenticationPolicy) class IACLAuthorizationPolicyDirective(Interface): pass def aclauthorizationpolicy(_context): policy = ACLAuthorizationPolicy() - _context.action( - discriminator = 'authorization_policy', - callable = handler, - args = ('registerUtility', policy, IAuthorizationPolicy, '', - _context.info), - ) + # authorization policies must be registered eagerly so they can be + # found by the view registration machinery + sm = getSiteManager() + sm.registerUtility(policy, IAuthorizationPolicy) + _context.action(discriminator=IAuthorizationPolicy) class IRouteDirective(Interface): """ The interface for the ``route`` ZCML directive """ name = TextLine(title=u'name', required=True) path = TextLine(title=u'path', required=True) + factory = GlobalObject(title=u'context factory', required=False) view = GlobalObject(title=u'view', required=False) view_for = GlobalObject(title=u'view_for', required=False) + view_permission = TextLine(title=u'view_permission', required=False) + view_request_type = TextLine(title=u'view_request_type', required=False) + view_request_method = TextLine(title=u'view_request_method', required=False) + view_containment = GlobalObject( + title = u'Dotted name of a containment class or interface', + required=False) + # alias for "view_for" + for_ = GlobalObject(title=u'for', required=False) + # alias for "view_permission" permission = TextLine(title=u'permission', required=False) - factory = GlobalObject(title=u'context factory', required=False) + # alias for "view_request_type" request_type = TextLine(title=u'request_type', required=False) - -def route(_context, name, path, view=None, view_for=None, permission=None, - factory=None, request_type=None): + # alias for "view_request_method" + request_method = TextLine(title=u'request_method', required=False) + # alias for "view_request_param" + request_param = TextLine(title=u'request_param', required=False) + # alias for "view_containment" + containment = GlobalObject( + title = u'Dotted name of a containment class or interface', + required=False) + +def route(_context, name, path, view=None, view_for=None, + permission=None, factory=None, request_type=None, for_=None, + view_permission=None, view_request_type=None, + request_method=None, view_request_method=None, + request_param=None, view_request_param=None, containment=None, + view_containment=None): """ Handle ``route`` ZCML directives """ + # the strange ordering of the request kw args above is for b/w + # compatibility purposes. + for_ = view_for or for_ + request_type = view_request_type or request_type + permission = view_permission or permission + request_method = view_request_method or request_method + request_param = view_request_param or request_param + containment = view_containment or containment + sm = getSiteManager() - request_factories = named_request_factories(name) - sm.registerUtility(request_factories, IRequestFactories, name=name) + + if request_type in ('GET', 'HEAD', 'PUT', 'POST', 'DELETE'): + # b/w compat for 1.0 + request_method = request_type + request_type = None + + if request_type is None: + request_factory = queryUtility(IRouteRequest, name=name) + if request_factory is None: + request_factory = create_route_request_factory(name) + sm.registerUtility(request_factory, IRouteRequest, name=name) + request_type = implementedBy(request_factory) if view: - _view(_context, permission, view_for, view, '', request_type, name) + _view(_context, permission=permission, for_=for_, view=view, name='', + request_type=request_type, route_name=name, + request_method=request_method, request_param=request_param, + containment=containment) _context.action( - discriminator = ('route', name, view_for, request_type), + discriminator = ('route', name), callable = connect_route, args = (path, name, factory), ) @@ -361,6 +553,10 @@ class IStaticDirective(Interface): required=False, default=None) +class StaticRootFactory: + def __init__(self, environ): + pass + def static(_context, name, path, cache_max_age=3600): """ Handle ``static`` ZCML directives """ @@ -371,7 +567,8 @@ def static(_context, name, path, cache_max_age=3600): path = '%s:%s' % (package_name(_context.resolve('.')), path) view = static_view(path, cache_max_age=cache_max_age) - route(_context, name, "%s*subpath" % name, view=view) + route(_context, name, "%s*subpath" % name, view=view, + view_for=StaticRootFactory, factory=StaticRootFactory) class IViewDirective(Interface): for_ = GlobalObject( @@ -394,8 +591,7 @@ class IViewDirective(Interface): name = TextLine( title=u"The name of the view", description=u""" - The name shows up in URLs/paths. For example 'foo' or - 'foo.html'.""", + The name shows up in URLs/paths. For example 'foo' or 'foo.html'.""", required=False, ) @@ -411,6 +607,25 @@ class IViewDirective(Interface): title = u'The route that must match for this view to be used', required = False) + containment = GlobalObject( + title = u'Dotted name of a containment class or interface', + required=False) + + request_method = TextLine( + title = u'Request method name that must be matched (e.g. GET/POST)', + description = (u'The view will be called if and only if the request ' + 'method (``request.method``) matches this string. This' + 'functionality replaces the older ``request_type`` ' + 'functionality.'), + required=False) + + request_param = TextLine( + title = (u'Request parameter name that must exist in ' + '``request.params`` for this view to match'), + description = (u'The view will be called if and only if the request ' + 'parameter exists which matches this string.'), + required=False) + class INotFoundViewDirective(Interface): view = GlobalObject( title=u"", @@ -523,10 +738,14 @@ class BFGViewFunctionGrokker(martian.InstanceGrokker): name = obj.__view_name__ request_type = obj.__request_type__ route_name = obj.__route_name__ + request_method = obj.__request_method__ + request_param = obj.__request_param__ + containment = obj.__containment__ context = kw['context'] view(context, permission=permission, for_=for_, view=obj, name=name, request_type=request_type, - route_name=route_name) + route_name=route_name, request_method=request_method, + request_param=request_param, containment=containment) return True return False @@ -535,47 +754,6 @@ def exclude(name): return True return False -def requestonly(class_or_callable): - """ Return true of the class or callable accepts only a request argument, - as opposed to something that accepts context, request """ - if inspect.isfunction(class_or_callable): - fn = class_or_callable - elif inspect.isclass(class_or_callable): - try: - fn = class_or_callable.__init__ - except AttributeError: - return False - else: - try: - fn = class_or_callable.__call__ - except AttributeError: - return False - - try: - argspec = inspect.getargspec(fn) - except TypeError: - return False - - args = argspec[0] - defaults = argspec[3] - - if hasattr(fn, 'im_func'): - # it's an instance method - if not args: - return False - args = args[1:] - if not args: - return False - - if len(args) == 1: - return True - - elif args[0] == 'request': - if len(args) - len(defaults) == 1: - return True - - return False - class Uncacheable(object): """ Include in discriminators of actions which are not cacheable; this class only exists for backwards compatibility (<0.8.1)""" |
