Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added code to hide unauthenticated and disallowed endpoints #68

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ Not yet released.
- Removes `mimerender`_ as a dependency.
- :issue:`7`: allows filtering before function evaluation.
- :issue:`49`: deserializers now expect a complete JSON API document.
- :issue:`60`: added the ``hide_disallowed_endpoints`` keyword argument to
:meth:`APIManager.create_api_blueprint` to hide disallowed HTTP methods
behind a :http:status:`404` response instead of a :http:status:`405`
response.
- :issue:`200`: be smarter about determining the ``collection_name`` for
polymorphic models defined with single-table inheritance.
- :issue:`253`: don't assign to callable attributes of models.
Expand Down Expand Up @@ -60,10 +64,10 @@ Version 1.0.0b1
Released on April 2, 2016.

- :issue:`255`: adds support for filtering by PostgreSQL network operators.
- :issue:`257`: ensures additional attributes specified by the user actually exist on
the model.
- :issue:`363` (partial solution): don't use ``COUNT`` on requests that don't require
pagination.
- :issue:`257`: ensures additional attributes specified by the user actually
exist on the model.
- :issue:`363` (partial solution): don't use ``COUNT`` on requests that don't
require pagination.
- :issue:`404`: **Major overhaul of Flask-Restless to support JSON API**.
- Increases minimum version requirement for ``python-dateutil`` to be strictly
greater than 2.2 to avoid parsing bug.
Expand Down
2 changes: 1 addition & 1 deletion docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ currently include versions 2.6, 2.7, 3.3, 3.4, and 3.5.
Flask-Restless has the following dependencies (which will be automatically
installed if you use ``pip``):

* `Flask`_ version 0.10 or greater
* `Flask`_ version 0.11 or greater
* `SQLAlchemy`_ version 0.8 or greater
* `python-dateutil`_ version strictly greater than 2.2

Expand Down
22 changes: 21 additions & 1 deletion flask_restless/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import sys

from sqlalchemy.inspection import inspect
from flask import abort
from flask import Blueprint
from flask import url_for as flask_url_for

Expand Down Expand Up @@ -453,7 +454,8 @@ def create_api_blueprint(self, name, model, methods=READONLY_METHODS,
serializer_class=None, deserializer_class=None,
includes=None, allow_to_many_replacement=False,
allow_delete_from_to_many_relationships=False,
allow_client_generated_ids=False):
allow_client_generated_ids=False,
hide_disallowed_endpoints=False):
"""Creates and returns a ReSTful API interface as a blueprint, but does
not register it on any :class:`flask.Flask` application.

Expand Down Expand Up @@ -648,6 +650,18 @@ def create_api_blueprint(self, name, model, methods=READONLY_METHODS,
this be a UUID. This is ``False`` by default. For more information, see
:doc:`creating`.

If `hide_disallowed_endpoints` is ``True``, requests to
disallowed methods (that is, methods not specified in
`methods`), which would normally yield a :http:status:`405`
response, will yield a :http:status:`404` response instead. This
option may be used as a simple form of "security through
obscurity", by (slightly) hindering users from discovering where
an endpoint exists.

.. versionchanged:: 1.0.0

The `hide_disallowed_endpoints` keyword argument is new.

"""
# Perform some sanity checks on the provided keyword arguments.
if only is not None and exclude is not None:
Expand Down Expand Up @@ -827,11 +841,17 @@ def create_api_blueprint(self, name, model, methods=READONLY_METHODS,
blueprint.add_url_rule(eval_endpoint, methods=eval_methods,
view_func=eval_api_view)

if hide_disallowed_endpoints:
@blueprint.errorhandler(405)
def return_404(error):
abort(404)

# Finally, record that this APIManager instance has created an API for
# the specified model.
self.created_apis_for[model] = APIInfo(collection_name, blueprint.name,
serializer, primary_key)
self.models.add(model)

return blueprint

def create_api(self, *args, **kw):
Expand Down
2 changes: 1 addition & 1 deletion requirements/install.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
flask>=0.10
flask>=0.11
flask-sqlalchemy>=0.10
sqlalchemy>=0.8
python-dateutil>2.2
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

#: The installation requirements for Flask-Restless. Flask-SQLAlchemy is not
#: required, so the user must install it explicitly.
REQUIREMENTS = ['flask>=0.10', 'sqlalchemy>=0.8', 'python-dateutil>2.2']
REQUIREMENTS = ['flask>=0.11', 'sqlalchemy>=0.8', 'python-dateutil>2.2']

#: The absolute path to this file.
HERE = os.path.abspath(os.path.dirname(__file__))
Expand Down
23 changes: 23 additions & 0 deletions tests/test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,29 @@ def test_disallowed_methods(self):
response = func('/api/person')
assert response.status_code == 405

def test_hide_disallowed_endpoints(self):
"""Test for hiding disallowed endpoints behind a 404 Not Found.

Setting the `hide_disallowed_endpoints` keyword argument to True
should cause requests that would normally cause a
:http:status:`405` response to cause a :http:status:`404`
response instead.

"""
self.manager.create_api(self.Person, hide_disallowed_endpoints=True)

response = self.app.get('/api/person')
self.assertNotEqual(response.status_code, 404)

response = self.app.post('/api/person')
self.assertEqual(response.status_code, 404)
response = self.app.patch('/api/person/1')
self.assertEqual(response.status_code, 404)
response = self.app.delete('/api/person/1')
self.assertEqual(response.status_code, 404)
response = self.app.put('/api/person/1')
self.assertEqual(response.status_code, 404)

def test_empty_collection_name(self):
"""Tests that calling :meth:`APIManager.create_api` with an empty
collection name raises an exception.
Expand Down