arxiv.users.auth.decorators module¶
Scope-based authorization of user/client requests.
This module provides scoped()
, a decorator factory used to protect Flask
routes for which authorization is required. This is done by specifying a
required authorization scope (see arxiv.users.auth.scopes
) and/or by
providing a custom authorizer function.
For routes that involve specific resources, a resource
callback should also
be provided. That callback function should accept the same arguments as the
route function, and return the identifier for the resource as a string.
Using scoped()
with an authorizer function allows you to define
application-specific authorization logic on a per-request basis without adding
complexity to request controllers. The call signature of the authorizer
function should be: (session: domain.Session, *args, **kwargs) -> bool
,
where *args and **kwargs are the positional and keyword arguments,
respectively, passed by Flask to the decorated route function (e.g. the
URL parameters).
Note
The authorizer function is only called if the session does not have a global or resource-specific instance of the required scope, or if a required scope is not specified.
Here’s an example of how you might use this in a Flask application:
from arxiv.users.auth.decorators import scoped
from arxiv.users.auth import scopes
from arxiv.users import domain
def is_owner(session: domain.Session, user_id: str, **kwargs) -> bool:
'''Check whether the authenticated user matches the requested user.'''
return session.user.user_id == user_id
def get_resource_id(user_id: str) -> str:
'''Get the user ID from the request.'''
return user_id
def redirect_to_login(user_id: str) -> Response:
'''Send the unauthorized user to the log in page.'''
return url_for('login')
@blueprint.route('/<string:user_id>/profile', methods=['GET'])
@scoped(scopes.EDIT_PROFILE, resource=get_resource_id,
authorizer=user_is_owner, unauthorized=redirect_to_login)
def edit_profile(user_id: str):
'''User can update their account information.'''
data, code, headers = profile.get_profile(user_id)
return render_template('accounts/profile.html', **data)
When the decorated route function is called…
- If no session is available from either the middleware or the legacy database,
the
unauthorized
callback is called, and/orUnauthorized
exception is raised. - If a required scope was provided, the session is checked for the presence of
that scope in this order:
- Global scope (:*), e.g. for administrators.
- Resource-specific scope (:[resource_id]), i.e. explicitly granted for a particular resource.
- Generic scope (no resource part).
- If an authorization function was provided, the function is called only if a required scope was not provided, or if only the generic scope was found.
- Session data is added directly to the Flask request object as
request.session
, for ease of access elsewhere in the application. - Finally, if no exceptions have been raised, the route is called with the original parameters.
-
arxiv.users.auth.decorators.
scoped
(required=None, resource=None, authorizer=None, unauthorized=None)[source]¶ Generate a decorator to enforce authorization requirements.
Parameters: - required (str) – The scope required on a user or client session in order use the
decorated route. See
arxiv.users.auth.scopes
. If not provided, no scope will be enforced. - resource (function) – If a route provides actions for a specific resource, a callable should be provided that accepts the route arguments and returns the resource identifier (str).
- authorizer (function) – In addition, an authorizer function may be passed to provide more
specific authorization checks. For example, this function may check
that the requesting user is the owner of a resource. Should have the
signature:
(session: domain.Session, *args, **kwargs) -> bool
.*args
and**kwargs
are the parameters passed to the decorated function. If the authorizer returnsFalse
, anexception is raised.
- unauthorized (function) – A callback may be passed to handle cases in which the request is
unauthorized. This function will be passed the same arguments as the
original route function. If the callback returns anything other than
None
, the return value will be treated as a response and execution will stop. Otherwise, anUnauthorized
exception will be raised. If a callback is not provided (default) anUnauthorized
exception will be raised.
Returns: A decorator that enforces the required scope and calls the (optionally) provided authorizer.
Return type: function
Return type: - required (str) – The scope required on a user or client session in order use the
decorated route. See