diff --git a/.gitignore b/.gitignore index 1479ae3..14485b0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ local.env *.egg-info www/appendices/disclosures.json +tests/.cache diff --git a/.travis.yml b/.travis.yml index 952297c..7f96f8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,21 @@ language: python +sudo: false + python: - "2.7" install: + - if [ "${TRAVIS_BRANCH}" = "master" -a "${TRAVIS_PULL_REQUEST}" = "false" ]; then rm -rf env; fi + - touch requirements.txt requirements_tests.txt - make env -before_script: - - make run& - - sleep 3 # Give webserver some time to start +cache: + directories: + - env/bin + - env/lib/python2.7/site-packages -# Dummy command until we have real tests -script: - - curl http://localhost:8536/ +script: make test branches: only: @@ -41,5 +44,3 @@ notifications: template: - "%{repository} (%{branch}:%{commit} by %{author}): %{message} (%{build_url})" skip_join: true - -sudo: false diff --git a/Makefile b/Makefile index b1335df..b0a4d56 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ bin_dir := $(shell $(python) -c 'import sys; bin = "Scripts" if sys.platform == env_bin := env/$(bin_dir) venv := "./vendor/virtualenv-12.0.7.py" -env: +env: requirements.txt requirements_tests.txt $(python) $(venv)\ --prompt="[inside.gratipay.com] " \ --never-download \ @@ -14,6 +14,7 @@ env: ./env/ ./$(env_bin)/pip --version ./$(env_bin)/pip install -f file:///$(PWD)/vendor -r requirements.txt + ./$(env_bin)/pip install -f file:///$(PWD)/vendor -r requirements_tests.txt clean: rm -rf env @@ -22,3 +23,6 @@ clean: run: env ./$(env_bin)/honcho -e defaults.env,local.env run ./env/bin/python \ ./startapp.py --port=8536 + +test: + ./$(env_bin)/py.test ./tests/ diff --git a/configure-aspen.py b/configure-aspen.py index 9d9b333..da598cd 100644 --- a/configure-aspen.py +++ b/configure-aspen.py @@ -3,6 +3,7 @@ import random from os.path import basename, dirname, join, realpath, isdir +from aspen import Response import canonizer import gfm from nav import NavItem @@ -35,8 +36,12 @@ def add_nav_current_to_context(dispatch_result, website): def add_nav_next_to_context(nav_current, website): return {'nav_next': nav_current.next_child} -add_nav_to_website(website) +def only_allow_certain_methods(request): + whitelisted = ['GET', 'HEAD', 'POST'] + if request.method.upper() not in whitelisted: + raise Response(405) +website.algorithm.insert_before('dispatch_request_to_filesystem', only_allow_certain_methods) website.algorithm.insert_after('dispatch_request_to_filesystem', add_nav_to_website) website.algorithm.insert_after('add_nav_to_website', add_nav_current_to_context) website.algorithm.insert_after('add_nav_current_to_context', add_nav_next_to_context) diff --git a/requirements_tests.txt b/requirements_tests.txt new file mode 100644 index 0000000..7a629d7 --- /dev/null +++ b/requirements_tests.txt @@ -0,0 +1 @@ +pytest==2.9.2 diff --git a/tests/py/test_security.py b/tests/py/test_security.py new file mode 100644 index 0000000..e6cd9b6 --- /dev/null +++ b/tests/py/test_security.py @@ -0,0 +1,18 @@ +from __future__ import unicode_literals +from pytest import yield_fixture + +from aspen.testing.client import Client + +@yield_fixture +def client(): + yield Client(www_root='www', project_root='') + +def test_disallowed_methods(client): + for disallowed in ['TRACE', 'trAce', 'DELETE', 'PUT', 'OPTIONS', 'JUNK']: + response = client.hxt(disallowed, '/') + assert response.code == 405 + +def test_allowed_methods(client): + for allowed in ['GET', 'gEt', 'POST', 'HEAD']: + response = client.hit('GET', trace='/') + assert response.code == 200