Commit 0950a6ce authored by adietmue's avatar adietmue
Browse files

Validation: Put rules in extra file validation.py

parent 8e2e8d7f
......@@ -10,16 +10,15 @@ Next, you should check out the following files:
The data model and `Eve` configuration.
- `security.py`:
Authentication and Data Validation functions that are used in the model are
defined here. In particular, the interaction with AMIVAPI is handled here.
Authentication is defined here, in particular the interaction with AMIVAPI.
"""
from os import getcwd
from eve import Eve
from flask import Config
from security import APIAuth, APIValidator, only_own_signups
from security import APIAuth, only_own_nethz
from validation import APIValidator
def create_app(settings=None):
......@@ -38,9 +37,11 @@ def create_app(settings=None):
# Eve provides hooks at several points of the request,
# we use this do add dynamic filtering
for method in ['GET', 'PATCH', 'DELETE']:
event = getattr(application, 'on_pre_%s_signups' % method)
event += only_own_signups
for resource in ['signups', 'selections']:
for method in ['GET', 'PATCH', 'DELETE']:
event = getattr(application,
'on_pre_%s_%s' % (method, resource))
event += only_own_nethz
return application
......
......@@ -15,19 +15,13 @@ The important bits are:
Take a look at the [Eve docs](http://python-eve.org/authentication.html) for
more info.
- Additional Validation rules
More info [here](http://python-eve.org/validation.html).
"""
from functools import wraps
import json
import requests
from eve.auth import TokenAuth
from eve.io.mongo import Validator
from flask import request, g, current_app, abort
from flask import g, current_app, abort
# Requests to AMIVAPI
......@@ -134,51 +128,11 @@ class APIAuth(TokenAuth):
abort(403)
# Dynamic Filter
# Dynamic Visibility Filter
def only_own_signups(_, lookup):
def only_own_nethz(_, lookup):
"""Users can only see signups if their ID matches."""
if not is_admin():
# Add the additional lookup with an `$and` condition
# or extend existing `$and`s
lookup.setdefault('$and', []).append({'nethz': get_nethz()})
# Validation
class APIValidator(Validator):
"""Provide a rule to check nethz of current user."""
def _validate_only_own_nethz(self, enabled, field, value):
"""If the user is no admin, only own nethz is allowed for singup."""
if enabled and not is_admin():
if value != get_nethz():
self._error(field,
"You can only use your own nethz to sign up.")
def _validate_unique_combination(self, unique_combination, field, value):
"""Validate that a combination of fields is unique.
Code is copy-pasted from amivapi, see there for more explanation.
https://github.com/amiv-eth/amivapi/blob/master/amivapi/utils.py
"""
lookup = {field: value} # self
for other_field in unique_combination:
lookup[other_field] = self.document.get(other_field)
if request.method == 'PATCH':
original = self._original_document
for key in unique_combination:
if key not in self.document.keys():
lookup[key] = original[key]
resource = self.resource
if current_app.data.find_one(resource, None, **lookup) is not None:
self._error(field, "value already exists in the database in " +
"combination with values for: %s" %
unique_combination)
def _validate_not_patchable(self, enabled, field, _):
"""Inhibit patching of the field, also copied from AMIVAPI."""
if enabled and (request.method == 'PATCH'):
self._error(field, "this field can not be changed with PATCH")
......@@ -3,7 +3,9 @@
Check out [the Eve docs for configuration](http://python-eve.org/config.html)
if you are unsure about some of the settings.
Several validation rules are still missing, they are marked with TODO in the
Our schema requires customized data validation. These validation rules are
implemented in `validation.py`.
Some validation rules are still missing, they are marked with TODO in the
schema directly.
"""
......
......@@ -7,6 +7,7 @@ from flask import g
ALL_RESOURCES = ['lectures', 'courses', 'signups', 'selections', 'payments']
ADMIN_RESOURCES = ['lectures', 'courses'] # only admin can write
PERSONAL_RESOURCES = ['signups', 'selections'] # users can only see their own
@pytest.mark.parametrize('resource', ALL_RESOURCES)
......@@ -123,22 +124,23 @@ def test_selection_for_own_nethz_only(app):
assert_status=201)
def test_user_signup_visibility(app):
"""Test that we a user cannot see others' signups."""
@pytest.mark.parametrize('resource', PERSONAL_RESOURCES)
def test_user_visibility(app, resource):
"""Test that a user cannot see others' signups or selections."""
nethz = 'Something'
with app.user(nethz=nethz):
# Create fake signup with different nethz
own = str(app.data.driver.db['signups'].insert({'nethz': nethz}))
other = str(app.data.driver.db['signups'].insert({'nethz': 'trolo'}))
own = str(app.data.driver.db[resource].insert({'nethz': nethz}))
other = str(app.data.driver.db[resource].insert({'nethz': 'trolo'}))
# Resource: Can only see own, not both signups
response = app.client.get('/signups', assert_status=200)
response = app.client.get('/' + resource, assert_status=200)
assert len(response['_items']) == 1
assert response['_items'][0]['nethz'] == nethz
# Items
own_url = '/signups/' + own
other_url = '/signups/' + other
own_url = '/%s/%s' % (resource, own)
other_url = '/%s/%s' % (resource, other)
# Get
app.client.get(own_url, assert_status=200)
......@@ -153,22 +155,23 @@ def test_user_signup_visibility(app):
app.client.delete(other_url, assert_status=404)
def test_admin_signup_visibility(app):
"""Test that we an admin can see others' signups."""
with app.admin():
@pytest.mark.parametrize('resource', PERSONAL_RESOURCES)
def test_admin_signup_visibility(app, resource):
"""Test that we an admin can see others' signups and selections."""
with app.admin(nethz='somethingsomething'):
headers = {'If-Match': 'Wrong'}
# Create fake signup with different nethz
other = str(app.data.driver.db['signups'].insert({'nethz': 'trolo'}))
other = str(app.data.driver.db[resource].insert({'nethz': 'trolo'}))
# Resource: Can see signups
response = app.client.get('/signups',
response = app.client.get('/' + resource,
headers=headers,
assert_status=200)
assert len(response['_items']) == 1
# Items
url = '/signups/' + other
url = '/%s/%s' % (resource, other)
# Get
app.client.get(url, headers=headers, assert_status=200)
......
"""Custom Validators
We require several custom validation rules, e.g. that user can only use their
own nethz.
Learn how validation works [here](http://python-eve.org/validation.html).
TODO: Several validation rules still need to be implemented. See `settings.py`.
"""
from flask import request, current_app
from eve.io.mongo import Validator
from security import is_admin, get_nethz
class APIValidator(Validator):
"""Provide a rule to check nethz of current user."""
def _validate_only_own_nethz(self, enabled, field, value):
"""If the user is no admin, only own nethz is allowed for singup."""
if enabled and not is_admin():
if value != get_nethz():
self._error(field,
"You can only use your own nethz to sign up.")
def _validate_unique_combination(self, unique_combination, field, value):
"""Validate that a combination of fields is unique.
Code is copy-pasted from amivapi, see there for more explanation.
https://github.com/amiv-eth/amivapi/blob/master/amivapi/utils.py
"""
lookup = {field: value} # self
for other_field in unique_combination:
lookup[other_field] = self.document.get(other_field)
if request.method == 'PATCH':
original = self._original_document
for key in unique_combination:
if key not in self.document.keys():
lookup[key] = original[key]
resource = self.resource
if current_app.data.find_one(resource, None, **lookup) is not None:
self._error(field, "value already exists in the database in " +
"combination with values for: %s" %
unique_combination)
def _validate_not_patchable(self, enabled, field, value):
"""Inhibit patching of the field, also copied from AMIVAPI."""
if enabled and (request.method == 'PATCH'):
self._error(field, "this field can not be changed with PATCH")
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment