This repository has been archived by the owner on Jul 21, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
215 additions
and
199 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
.. _failure: | ||
|
||
|
||
=================== | ||
Controlling Failure | ||
=================== | ||
|
||
|
||
When dealing with permissioning, failure is an expected and desired outcome. And | ||
Flask-Allows provides several measures to deal with this failure. | ||
|
||
|
||
********************* | ||
Throwing an exception | ||
********************* | ||
|
||
The first measure is the ability to configure Requirement runners to throw an | ||
exception. By default this will be werkzeug's Forbidden exception. However, | ||
this can be set to be any exception type or specific instance. The easiest | ||
way to set this is through the :class:`~flask_allows.allows.Allows` constructor:: | ||
|
||
class PermissionError(Exception): | ||
def __init__(self): | ||
super().__init__("I'm sorry Dave, I'm afraid I can't do that") | ||
|
||
|
||
allows = Allows(throws=PermissionError) | ||
|
||
# alternatively | ||
allows = Allows(throws=PermissionError()) | ||
|
||
|
||
If a particular exception is desirable most of but not all of the time, an | ||
exception type or instance can be provided each Requirement runner:: | ||
|
||
# to Permission helper | ||
Permission(SomeRequirement(), throws=PermissionError) | ||
|
||
# to decorators | ||
@allows.requires(SomeRequirement(), throws=PermissionError) | ||
@requires(SomeRequirement(), throws=PermissionError) | ||
|
||
|
||
**************** | ||
Failure Callback | ||
**************** | ||
|
||
Another way to handle failure is providing an ``on_fail`` argument that will be | ||
invoked when failure happens. The value provided to ``on_fail`` doesn't have to | ||
be a callable, so any value is appropriate. If the value provided is a callable | ||
it should be prepared to accept any arbitrary arguments that were provided when | ||
the requirement runner that was invoked. | ||
|
||
To add a failure callback, it can be provided to the | ||
:class:`~flask_allows.allows.Allows` constructor:: | ||
|
||
def flash_failure_message(*args, **kwargs): | ||
flash("I'm sorry Dave, I'm afraid I can't do that", "error") | ||
|
||
allows = Allows(on_fail=flash_failure_message) | ||
|
||
|
||
If ``on_fails`` return a non-``None`` value, that will be used as the return | ||
value from the requirement runner. However, if a ``None`` is returned from the | ||
callback, then the configured exception is raised instead. In the above example, | ||
since a ``None`` is implicitly returned from the callback, a werkzeug Forbidden | ||
exception would be raised from any requirements runners. | ||
|
||
An example of returning a value from the callback would be returning a redirect | ||
to another page:: | ||
|
||
|
||
def redirect_to_home(*args, **kwargs): | ||
flash("I'm sorry Dave, I'm afraid I can't do that", "error") | ||
return redirect(url_for("index")) | ||
|
||
However, any value can be returned from this wrapper. | ||
|
||
.. note:: | ||
|
||
When used with the :class:`~flask_allows.permission.Permission` helper, | ||
the callback will be invoked with no arguments and the return value isn't | ||
considered. | ||
|
||
.. danger:: | ||
|
||
When using ``on_fail`` with route decorators, be sure to return an | ||
appropriate value for Flask to turn into a response. | ||
|
||
Similar to exception configuration, ``on_fail`` can be passed to any requirements | ||
runner:: | ||
|
||
# to Permission helper | ||
Permission(SomeRequirement(), on_fail=flash_failure_message) | ||
|
||
# to decorators | ||
@allow.requires(SomeRequirement(), on_fail=redirect_to_home) | ||
@requires(SomeRequirement(), on_fail=redirect_to_home) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
.. _helpers: | ||
|
||
|
||
####### | ||
Helpers | ||
####### | ||
|
||
In addition to the :class:`~flask_allows.allows.Allows`, there are several | ||
helper classes and functions available. | ||
|
||
|
||
********** | ||
Permission | ||
********** | ||
|
||
:class:`~flask_allows.permission.Permission` enables checking permissions as a | ||
boolean or controlling access to sections of code with a context manager. To | ||
construct a Permission, provide it with a collection of requirements to enforce | ||
and optionally any combination of: | ||
|
||
- ``on_fail`` callback | ||
- An exception type or instance with ``throws`` | ||
- A specific identity to check against | ||
|
||
.. note:: | ||
|
||
Constructing a ``Permission`` object requires an application context as it | ||
gathers defaults from the configured Allows extension at construction time. | ||
|
||
Once configured, the Permission object can be used as if it were a boolean:: | ||
|
||
p = Permission(SomeRequirement()) | ||
|
||
if p: | ||
print("Passed!") | ||
else: | ||
print("Failed!") | ||
|
||
When using Permission as a boolean, only the requirement checks are run but no | ||
failure handling is run as not entering the conditional block is considered | ||
handling the failure. Not running the failure handling on a failed conditional | ||
check also helps cut down on unexpected side effects. | ||
|
||
|
||
If you'd like the failure handlers to be run, Permission can also be used as a | ||
context manager:: | ||
|
||
p = Permission(SomeRequirement()) | ||
|
||
with p: | ||
print("Passed!") | ||
|
||
When used as a context manager, if the requirements provided are not met then | ||
the registered ``on_fail`` callback is run and the registered exception type | ||
is raised. | ||
|
||
.. note:: | ||
|
||
Permission ignores the result of the callback when used as a context | ||
manager so the exception type is always raised unless the callback raises | ||
an exception instead. | ||
|
||
|
||
******** | ||
requires | ||
******** | ||
|
||
If you're using factory methods to create your Flask application and extensions, | ||
it's often difficult to get ahold of a reference to the allows object. Because | ||
of this, the :func:`~flask_allows.view.requires` helper exists as well. This | ||
is a function that calls the configured allows object when the wrapped function | ||
is invoked:: | ||
|
||
@requires(SomeRequirement()) | ||
def random(): | ||
return 4 | ||
|
||
.. danger:: | ||
|
||
If you're using ``requires`` to guard route handlers, the :func:``route`` | ||
decorator must be applied at the top of the decorator stack (visually first, | ||
logically last):: | ||
|
||
@app.route('/') | ||
@requires(SomeRequirement()) | ||
def index(): | ||
pass | ||
|
||
If the ``requires`` decorator comes after the ``route`` decorator, then the | ||
unguarded function is registered into the application:: | ||
|
||
@requires(SomeRequirement()) | ||
@app.route('/') | ||
def index(): | ||
pass | ||
|
||
This invocation registers the actual ``index`` function into the routing | ||
map and then decorates the index function. | ||
|
||
|
||
The ``requires`` decorator can also be applied to class based views by either | ||
adding it to the ``decorators`` property:: | ||
|
||
class SomeView(View): | ||
decorators = [requires(SomeRequirement())] | ||
|
||
Or by decorating individual methods:: | ||
|
||
class SomeView(MethodView): | ||
|
||
@requires(SomeRequirement()) | ||
def get(self): | ||
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,7 @@ Content | |
:maxdepth: 2 | ||
|
||
quickstart | ||
creating_requirements | ||
using_requirements | ||
helpers | ||
failure | ||
api | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.