Flask
The Oso Flask integration provides a more convenient interface to Oso for usage with Flask.
Installation
The Oso Flask integration is available on
PyPI and can be installed using
pip
:
$ pip install flask-oso
Usage
Initialization
The FlaskOso
class is the entrypoint to the integration.
It must be initialized with the Flask app and Oso:
from flask import Flask
from oso import Oso
app = Flask("app")
oso = Oso()
flask_oso = FlaskOso(app=app, oso=oso)
Alternatively, to support the Flask factory pattern, the
init_app()
method can be used:
from flask import Flask
from oso import Oso
from flask_oso import FlaskOso
oso = Oso()
flask_oso = FlaskOso(oso=oso)
def create_app():
app = Flask("app")
# Initialize Oso for this application
flask_oso.init_app(app)
return app
app = create_app()
This factory function can be a useful place for loading policy files, and
calling configuration functions on FlaskOso
like
flask_oso.FlaskOso.require_authorization()
:
def create_app():
app = Flask("app")
flask_oso.init_app(app)
flask_oso.require_authorization(app)
oso.load_file("authorization.polar")
oso.register_class(Expense)
Performing authorization
When using the flask-oso
integration, the primary authorization function is
flask_oso.FlaskOso.authorize()
. It accepts the same arguments as
is_allowed()
, but provides sensible defaults for working with
Flask. The actor defaults to flask.g.current_user
(this can be
customized, see set_get_actor()
). The action
defaults to the method of the current request flask.request.method
.
resource
must be provided.
flask_oso.FlaskOso.authorize()
can be used within route handlers, or in
the data access layer, depending upon how you want to express authorization.
Here’s a basic example in a route:
@app.route("/<int:id>", methods=["GET"])
def get_expense(id):
expense = Expense.query.get(id)
if expense is None:
raise NotFound()
flask_oso.authorize(action="read", resource=expense)
return expense.json()
Notice we didn’t need to check the return value of authorize
. By default,
a failed authorization will return a ``403 Forbidden`` response for the current
request. This can be controlled with
set_unauthorized_action()
.
Working with LocalProxy
objects
When using a library that exposes the current user (or similar
authorization data) through LocalProxy
objects, such as
Flask-Login’s
current_user
, you might need to explicitly dereference the proxy
to pass the underlying object to Oso:
from flask_login import current_user
def create_app():
app = Flask("app")
flask_oso.init_app(app)
flask_oso.require_authorization(app)
# Dereference the current_user LocalProxy
flask_oso.set_get_actor(lambda: current_user._get_current_object())
oso.load_file("authorization.polar")
oso.register_class(User)
return app
By dereferencing the proxy, Oso will use the underlying object when determining authorization instead of the proxy object.
Requiring authorization
One downside to calling flask_oso.FlaskOso.authorize()
explicitly within route handlers is that the check might be forgotten. To help detect this, the
flask_oso.FlaskOso.require_authorization()
option can be enabled during
initialization. This will cause an oso.OsoError
to be raised if
a call to flask_oso.FlaskOso.authorize()
is not made during the
processing of a request.
Sometimes a route will not need authorization. To prevent this route from
causing an authorization error, call
flask_oso.FlaskOso.skip_authorization()
during request processing:
oso = Oso()
flask_oso = FlaskOso()
def create_app():
app = Flask("app")
flask_oso.init_app(app)
flask_oso.require_authorization(app)
oso.load_file("authorization.polar")
return app
app = create_app()
@app.route("/about")
def about():
flask_oso.skip_authorization()
return "about us"
Using decorators
Some developers may prefer a decorator-based API for performing authorization.
flask_oso
provides two decorators:
flask_oso.skip_authorization()
marks a route as not requiring
authorization. It is the decorator version of
flask_oso.FlaskOso.skip_authorization()
.
flask_oso.authorize()
decorates a route and calls
flask_oso.FlaskOso.authorize()
before the route body is entered. For
example:
from flask_oso import authorize
@authorize(resource="get_user")
@app.route("/user")
def get_user():
return "current user"
This decorator can be used if the resource is known before entering the request body.
Route authorization
One common usage of flask_oso.authorize()
is to perform authorization
based on the Flask request object:
from flask import request
@flask_oso.authorize(resource=request)
@app.route("/")
def route():
return "authorized"
A policy can then be written controlling authorization based on request attributes, like the path:
# Allow any actor to make a GET request to "/".
allow(_actor, "GET", _resource: Request{path: "/"});
To enforce route authorization on all requests (the equivalent of decorating
every route as we did above), use the
perform_route_authorization()
method during
initialization.
Example
Check out the Flask integration example app on GitHub: osohq/oso-flask-integration.
API Reference
The Flask API reference is automatically generated from the Oso Flask library source files.
Connect with us on Slack
If you have any questions, or just want to talk something through, jump into Slack. An Oso engineer or one of the thousands of developers in the growing community will be happy to help.