Skip to content

Commit

Permalink
add options to exclude specific HTTP methods from auth checks
Browse files Browse the repository at this point in the history
- useful for CORS-applicable situations where OPTIONS needs to have a normal response
  • Loading branch information
guruofgentoo committed Jul 6, 2021
1 parent 1cd0895 commit b66d090
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 3 deletions.
7 changes: 7 additions & 0 deletions docs/source/getting-started.rst
Expand Up @@ -37,6 +37,11 @@ Configuration

- Names arguments to be accepted by CLI user commands and passed to the model

- ``KEGAUTH_HTTP_METHODS_EXCLUDED``: List of HTTP methods to exclude from auth checks

- Useful for CORS-applicable situations, where it may be advantageous to respond normally
to an OPTIONS request. Then, auth will apply as expected on the ensuing GET/POST/PUT/etc.

- Email settings

- ``KEGAUTH_EMAIL_SITE_NAME = 'Keg Application'``: Used in email body if mail is enabled
Expand Down Expand Up @@ -371,6 +376,8 @@ Views
redirect to the login page
- A decorated class/blueprint may have a custom `on_authentication_failure` instance method instead
of passing one to the decorator
- ``KEGAUTH_HTTP_METHODS_EXCLUDED`` can be overridden at the individual decorator level by passing
``http_methods_excluded`` to the decorator's constructor

- ``requires_permissions``

Expand Down
4 changes: 4 additions & 0 deletions keg_auth/core.py
Expand Up @@ -107,6 +107,10 @@ def init_config(self, app):

app.config.setdefault('KEGAUTH_CLI_USER_ARGS', ['email'])

# HTTP methods to ignore during auth checks. This can be useful for excluding
# methods like OPTIONS during front-end API requests, for CORS compatibility.
app.config.setdefault('KEGAUTH_HTTP_METHODS_EXCLUDED', [])

# Use select2 for form selects in templates extending keg_auth/form-base.
app.config.setdefault('KEGAUTH_USE_SELECT2', True)

Expand Down
14 changes: 12 additions & 2 deletions keg_auth/libs/decorators.py
Expand Up @@ -21,10 +21,12 @@ class RequiresUser(object):
- @requires_user
- @requires_user()
- @requires_user(on_authentication_failure=lambda: flask.abort(400))
- @requires_user(http_methods_excluded=['OPTIONS'])
"""
def __init__(self, on_authentication_failure=None):
def __init__(self, on_authentication_failure=None, http_methods_excluded=None):
# defaults for these handlers are provided, but may be overridden here
self._on_authentication_failure = on_authentication_failure
self.http_methods_excluded = http_methods_excluded

def __call__(self, class_or_function):
# decorator may be applied to a class or a function, but the effect is different
Expand Down Expand Up @@ -113,6 +115,12 @@ def on_authentication_failure(self):
flask.abort(401)

def check_auth(self, instance=None):
methods_excluded = flask.current_app.config.get('KEGAUTH_HTTP_METHODS_EXCLUDED')
if self.http_methods_excluded is not None:
methods_excluded = self.http_methods_excluded
if flask.request.method in methods_excluded:
return

# if flask_login has an authenticated user in session, that's who we want
if flask_login.current_user.is_authenticated:
return
Expand Down Expand Up @@ -147,9 +155,11 @@ class RequiresPermissions(RequiresUser):
- @requires_permissions(custom_authorization_callable that takes user arg)
- @requires_permissions('token1', on_authorization_failure=lambda: flask.abort(404))
"""
def __init__(self, condition, on_authentication_failure=None, on_authorization_failure=None):
def __init__(self, condition, on_authentication_failure=None, on_authorization_failure=None,
http_methods_excluded=None):
super(RequiresPermissions, self).__init__(
on_authentication_failure=on_authentication_failure,
http_methods_excluded=http_methods_excluded,
)
self.condition = condition
self._on_authorization_failure = on_authorization_failure
Expand Down
5 changes: 5 additions & 0 deletions keg_auth/tests/test_views.py
Expand Up @@ -519,6 +519,11 @@ def test_blueprint_class_level(self):
client.get('/protected-class', status=405)
client.get('/protected-class2', status=302)

def test_user_requirement_excludes_method(self):
client = flask_webtest.TestApp(flask.current_app)
# should have a 200 response
client.options('/protected-class2')


class TestRequestLoaders(object):
def test_token_auth_no_token(self):
Expand Down
2 changes: 1 addition & 1 deletion keg_auth_ta/views.py
Expand Up @@ -18,7 +18,7 @@ def on_authentication_failure(self):
flask.abort(405)


@requires_user()
@requires_user(http_methods_excluded=['OPTIONS'])
class ProtectedBlueprint2(flask.Blueprint):
pass

Expand Down

0 comments on commit b66d090

Please sign in to comment.