Secure write access to /users endpoint
- Admins can PUT everything - Users can only PUT themselves - The 'auth' field is always taken from the original, and never overwritten by the PUT. It can be missing from the request, so you can GET and then PUT the same data. - Nobody can POST or DELETE users
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import urllib
|
||||
|
||||
from flask import g
|
||||
from eve.auth import TokenAuth
|
||||
from flask import g, current_app
|
||||
from werkzeug.exceptions import Forbidden
|
||||
from eve.utils import parse_request
|
||||
|
||||
from application.utils.authorization import user_has_role
|
||||
|
||||
@@ -29,6 +30,24 @@ def post_GET_user(request, payload):
|
||||
payload.data = json.dumps(json_data)
|
||||
|
||||
|
||||
def before_replacing_user(request, lookup):
|
||||
"""Loads the auth field from the database, preventing any changes."""
|
||||
|
||||
# Find the user that is being replaced
|
||||
req = parse_request('users')
|
||||
req.projection = json.dumps({'auth': 1})
|
||||
original = current_app.data.find_one('users', req, **lookup)
|
||||
|
||||
# Make sure that the replacement has a valid auth field.
|
||||
updates = request.get_json()
|
||||
assert updates is request.get_json() # We should get a ref to the cached JSON, and not a copy.
|
||||
|
||||
if 'auth' in original:
|
||||
updates['auth'] = copy.deepcopy(original['auth'])
|
||||
else:
|
||||
updates.pop('auth', None)
|
||||
|
||||
|
||||
def after_replacing_user(item, original):
|
||||
"""Push an update to the Algolia index when a user item is updated"""
|
||||
|
||||
@@ -42,7 +61,7 @@ def after_replacing_user(item, original):
|
||||
item.get('username'), item.get('_id'), ex)
|
||||
|
||||
|
||||
def before_getting_users(request, lookup):
|
||||
def check_user_access(request, lookup):
|
||||
"""Modifies the lookup dict to limit returned user info."""
|
||||
|
||||
# No access when not logged in.
|
||||
@@ -76,8 +95,10 @@ def after_fetching_user_resource(response):
|
||||
|
||||
|
||||
def setup_app(app):
|
||||
app.on_pre_GET_users += before_getting_users
|
||||
app.on_pre_GET_users += check_user_access
|
||||
app.on_post_GET_users += post_GET_user
|
||||
app.on_replace_users += after_replacing_user
|
||||
app.on_pre_PUT_users += check_user_access
|
||||
app.on_pre_PUT_users += before_replacing_user
|
||||
app.on_replaced_users += after_replacing_user
|
||||
app.on_fetched_item_users += after_fetching_user
|
||||
app.on_fetched_resource_users += after_fetching_user_resource
|
||||
|
@@ -705,8 +705,8 @@ users = {
|
||||
'cache_control': 'max-age=10,must-revalidate',
|
||||
'cache_expires': 10,
|
||||
|
||||
'resource_methods': ['GET', 'POST'],
|
||||
|
||||
'resource_methods': ['GET'],
|
||||
'item_methods': ['GET', 'PUT'],
|
||||
'public_methods': [],
|
||||
|
||||
# By default don't include the 'auth' field. It can still be obtained
|
||||
|
Reference in New Issue
Block a user