Source code for flask_open_directory.decorators
# -*- coding: utf-8 -*-
from typing import Any
from functools import wraps
from flask import current_app, abort
from .utils import username_from_request
from .model import Group
[docs]class DecoratorContext(dict):
"""A specialized dict, used in request decorators. The keys are accessible
using normal dict style or with attribute (dot) style syntax.
"""
def __getattr__(self, key: str) -> Any:
try:
return self[key]
except KeyError:
raise AttributeError(key)
def __setattr__(self, key: str, value: Any) -> None:
self[key] = value
def _pass_open_directory(fn):
"""Helper to pass the open_directory registered with the current application
into a decorator function.
"""
@wraps(fn)
def decorator(*args, **kwargs):
od = getattr(current_app, 'extensions', {}).get('open_directory')
if od is None:
raise TypeError()
return fn(od, *args, **kwargs)
return decorator
[docs]def pass_context(fn):
"""Helper that passes a :class:`DecoratorContex` with the
:class:`OpenDirectory` registered with the current application and the
``username`` for the request. This will pass the ``context`` as the first
arg to the decorator.
.. seealso:: :ref:`custom_decorators`
"""
@wraps(fn)
@_pass_open_directory
def decorator(open_directory, *args, **kwargs):
ctx = DecoratorContext(
open_directory=open_directory,
username=username_from_request()
)
return fn(ctx, *args, **kwargs)
return decorator
[docs]def requires_group(group_name):
"""Decorator to mark a flask route as requiring the authenticated user
to be apart of the specified group.
:param group_name: The group name to query the open_directory
connection for and is required that the username
(retrieved from ``Authorization`` header in the
request) is apart of the given group. If the
user is not then we abort with a ``401`` code.
Example::
# app.py
from flask import Flask
from open_directory_utils import OpenDirectory, utils, requires_group
app = Flask(__name__)
app.config['OPEN_DIRECTORY_SERVER'] = 'example.com'
od = OpenDirectory(app)
... other routes
@app.route('/admins')
@requires_group('administrators')
def admins_only():
return "Hello, '{}'. You are an administrator".format(
utils.username_from_request()
)
"""
def inner(fn):
@wraps(fn)
@pass_context
def decorator(ctx, *args, **kwargs):
od, username = ctx['open_directory'], ctx['username']
if username is not None:
group = od.query(Group).filter(group_name=group_name).first()
if group and group.has_user(username):
return fn(*args, **kwargs)
return abort(401)
return decorator
return inner
def _requires_groups(*group_names, _all=True):
"""Helper for ``requires_all_groups`` and ``requires_any_group`` decorators.
"""
if _all is True:
test_fn = all
else:
test_fn = any
def inner(fn):
@wraps(fn)
@pass_context
def decorator(ctx, *args, **kwargs):
od, username = ctx['open_directory'], ctx['username']
groups = [od.query(Group).filter(group_name=g).first() for g in
group_names]
if test_fn(map(lambda g: g.has_user(username), groups)):
return fn(*args, **kwargs)
return abort(401)
return decorator
return inner
[docs]def requires_all_groups(*group_names):
"""Decorator to mark a flask route as requiring the authenticated user
to be apart of all the passed in groups.
This is similar to the ``requires_group``, only it accepts multiple
groups to check against.
:param group_names: The group name(s) to query the open_directory
connection for and is required that the username
(retrieved from ``Authorization`` header in the
request) is apart of the given group. If the
user is not then we abort with a ``401`` code.
Example::
# app.py
from flask import Flask
from open_directory_utils import OpenDirectory, utils, \
requires_all_groups
app = Flask(__name__)
app.config['OPEN_DIRECTORY_SERVER'] = 'example.com'
open_directory = OpenDirectory(app)
@app.route('/office-admins')
@requires_all_groups('administrators', 'office')
def office_admins():
return "Hello, '{}'. You are an office administrator".format(
utils.username_from_request()
)
"""
return _requires_groups(*group_names, _all=True)
[docs]def requires_any_group(*group_names):
"""Decorator to mark a flask route as requiring the authenticated user
to be apart of any of the passed in groups.
This is similar to the ``requires_all_groups``, only it accepts multiple
groups to check against, and succeeds if the authenticated user is a member
of any of the specified groups.
:param group_names: The group name(s) to query the open_directory
connection for and is required that the username
(retrieved from ``Authorization`` header in the
request) is apart of the given group. If the
user is not then we abort with a ``401`` code.
Example::
# app.py
from flask import Flask
from open_directory_utils import OpenDirectory, utils, \
requires_any_group
app = Flask(__name__)
app.config['OPEN_DIRECTORY_SERVER'] = 'example.com'
open_directory = OpenDirectory(app)
@app.route('/office-or-admins')
@requires_any_group('administrators', 'office')
def office_or_admins():
return "Hello, '{}'. You're an administrator or office user".format(
utils.username_from_request()
)
"""
return _requires_groups(*group_names, _all=False)