Be less secretive about users; allow limited anonymous /users/id access.
Anonymous users can now obtain full_name and email fields from any user. Authenticated users can also obtain those fields from other users, and all info about themselves.
This commit is contained in:
parent
d5c2df371a
commit
cf203b04f8
@ -66,21 +66,32 @@ def check_user_access(request, lookup):
|
|||||||
|
|
||||||
# No access when not logged in.
|
# No access when not logged in.
|
||||||
current_user = g.get('current_user')
|
current_user = g.get('current_user')
|
||||||
if current_user is None:
|
current_user_id = current_user['user_id'] if current_user else None
|
||||||
raise Forbidden()
|
|
||||||
|
|
||||||
# Admins can do anything and get everything, except the 'auth' block.
|
# Admins can do anything and get everything, except the 'auth' block.
|
||||||
if user_has_role(u'admin'):
|
if user_has_role(u'admin'):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Only allow access to the current user.
|
if not lookup and not current_user:
|
||||||
if '_id' in lookup:
|
raise Forbidden()
|
||||||
if str(lookup['_id']) != str(current_user['user_id']):
|
|
||||||
raise Forbidden()
|
|
||||||
return
|
|
||||||
|
|
||||||
# Add a filter to only return the current user.
|
# Add a filter to only return the current user.
|
||||||
lookup['_id'] = current_user['user_id']
|
if '_id' not in lookup:
|
||||||
|
lookup['_id'] = current_user['user_id']
|
||||||
|
|
||||||
|
|
||||||
|
def check_put_access(request, lookup):
|
||||||
|
"""Only allow PUT to the current user, or all users if admin."""
|
||||||
|
|
||||||
|
if user_has_role(u'admin'):
|
||||||
|
return
|
||||||
|
|
||||||
|
current_user = g.get('current_user')
|
||||||
|
if not current_user:
|
||||||
|
raise Forbidden()
|
||||||
|
|
||||||
|
if str(lookup['_id']) != str(current_user['user_id']):
|
||||||
|
raise Forbidden()
|
||||||
|
|
||||||
|
|
||||||
def after_fetching_user(user):
|
def after_fetching_user(user):
|
||||||
@ -88,6 +99,23 @@ def after_fetching_user(user):
|
|||||||
# custom end-points.
|
# custom end-points.
|
||||||
user.pop('auth', None)
|
user.pop('auth', None)
|
||||||
|
|
||||||
|
current_user = g.get('current_user')
|
||||||
|
current_user_id = current_user['user_id'] if current_user else None
|
||||||
|
|
||||||
|
# Admins can do anything and get everything, except the 'auth' block.
|
||||||
|
if user_has_role(u'admin'):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Only allow full access to the current user.
|
||||||
|
if str(user['_id']) == str(current_user_id):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Remove all fields except public ones.
|
||||||
|
public_fields = {'full_name', 'email'}
|
||||||
|
for field in list(user.keys()):
|
||||||
|
if field not in public_fields:
|
||||||
|
del user[field]
|
||||||
|
|
||||||
|
|
||||||
def after_fetching_user_resource(response):
|
def after_fetching_user_resource(response):
|
||||||
for user in response['_items']:
|
for user in response['_items']:
|
||||||
@ -97,7 +125,7 @@ def after_fetching_user_resource(response):
|
|||||||
def setup_app(app):
|
def setup_app(app):
|
||||||
app.on_pre_GET_users += check_user_access
|
app.on_pre_GET_users += check_user_access
|
||||||
app.on_post_GET_users += post_GET_user
|
app.on_post_GET_users += post_GET_user
|
||||||
app.on_pre_PUT_users += check_user_access
|
app.on_pre_PUT_users += check_put_access
|
||||||
app.on_pre_PUT_users += before_replacing_user
|
app.on_pre_PUT_users += before_replacing_user
|
||||||
app.on_replaced_users += after_replacing_user
|
app.on_replaced_users += after_replacing_user
|
||||||
app.on_fetched_item_users += after_fetching_user
|
app.on_fetched_item_users += after_fetching_user
|
||||||
|
@ -707,7 +707,7 @@ users = {
|
|||||||
|
|
||||||
'resource_methods': ['GET'],
|
'resource_methods': ['GET'],
|
||||||
'item_methods': ['GET', 'PUT'],
|
'item_methods': ['GET', 'PUT'],
|
||||||
'public_methods': [],
|
'public_item_methods': ['GET'],
|
||||||
|
|
||||||
# By default don't include the 'auth' field. It can still be obtained
|
# By default don't include the 'auth' field. It can still be obtained
|
||||||
# using projections, though, so we block that in hooks.
|
# using projections, though, so we block that in hooks.
|
||||||
|
@ -7,6 +7,8 @@ from bson import tz_util, ObjectId
|
|||||||
|
|
||||||
from common_test_class import AbstractPillarTest, TEST_EMAIL_USER, TEST_EMAIL_ADDRESS
|
from common_test_class import AbstractPillarTest, TEST_EMAIL_USER, TEST_EMAIL_ADDRESS
|
||||||
|
|
||||||
|
PUBLIC_USER_FIELDS = {'full_name', 'email'}
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationTests(AbstractPillarTest):
|
class AuthenticationTests(AbstractPillarTest):
|
||||||
def test_make_unique_username(self):
|
def test_make_unique_username(self):
|
||||||
@ -169,7 +171,7 @@ class UserListTests(AbstractPillarTest):
|
|||||||
self.create_valid_auth_token('323456789abc123456789abc', 'other-token')
|
self.create_valid_auth_token('323456789abc123456789abc', 'other-token')
|
||||||
|
|
||||||
def test_list_all_users_anonymous(self):
|
def test_list_all_users_anonymous(self):
|
||||||
# Anonymous access should be denied.
|
# Listing all users should be forbidden
|
||||||
resp = self.client.get('/users')
|
resp = self.client.get('/users')
|
||||||
self.assertEqual(403, resp.status_code)
|
self.assertEqual(403, resp.status_code)
|
||||||
|
|
||||||
@ -211,9 +213,20 @@ class UserListTests(AbstractPillarTest):
|
|||||||
for user_info in users['_items']:
|
for user_info in users['_items']:
|
||||||
self.assertNotIn('auth', user_info)
|
self.assertNotIn('auth', user_info)
|
||||||
|
|
||||||
|
def test_user_anonymous(self):
|
||||||
|
from application.utils import remove_private_keys
|
||||||
|
|
||||||
|
# Getting a user should be limited to certain fields
|
||||||
|
resp = self.client.get('/users/123456789abc123456789abc')
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
|
||||||
|
user_info = json.loads(resp.data)
|
||||||
|
regular_info = remove_private_keys(user_info)
|
||||||
|
self.assertEqual(PUBLIC_USER_FIELDS, set(regular_info.keys()))
|
||||||
|
|
||||||
def test_own_user_subscriber(self):
|
def test_own_user_subscriber(self):
|
||||||
# Regular access should result in only your own info.
|
# Regular access should result in only your own info.
|
||||||
resp = self.client.get('/users/%s' % '123456789abc123456789abc',
|
resp = self.client.get('/users/123456789abc123456789abc',
|
||||||
headers={'Authorization': self.make_header('token')})
|
headers={'Authorization': self.make_header('token')})
|
||||||
user_info = json.loads(resp.data)
|
user_info = json.loads(resp.data)
|
||||||
|
|
||||||
@ -231,14 +244,19 @@ class UserListTests(AbstractPillarTest):
|
|||||||
self.assertNotIn('auth', user_info)
|
self.assertNotIn('auth', user_info)
|
||||||
|
|
||||||
def test_other_user_subscriber(self):
|
def test_other_user_subscriber(self):
|
||||||
# Requesting another user should be denied.
|
from application.utils import remove_private_keys
|
||||||
|
|
||||||
|
# Requesting another user should be limited to full name and email.
|
||||||
resp = self.client.get('/users/%s' % '223456789abc123456789abc',
|
resp = self.client.get('/users/%s' % '223456789abc123456789abc',
|
||||||
headers={'Authorization': self.make_header('token')})
|
headers={'Authorization': self.make_header('token')})
|
||||||
user_info = json.loads(resp.data)
|
user_info = json.loads(resp.data)
|
||||||
|
|
||||||
self.assertEqual(403, resp.status_code)
|
self.assertEqual(200, resp.status_code)
|
||||||
self.assertNotIn('auth', user_info)
|
self.assertNotIn('auth', user_info)
|
||||||
|
|
||||||
|
regular_info = remove_private_keys(user_info)
|
||||||
|
self.assertEqual(PUBLIC_USER_FIELDS, set(regular_info.keys()))
|
||||||
|
|
||||||
def test_put_user(self):
|
def test_put_user(self):
|
||||||
from application.utils import remove_private_keys
|
from application.utils import remove_private_keys
|
||||||
|
|
||||||
|
@ -53,11 +53,12 @@ class LocalAuthTest(AbstractPillarTest):
|
|||||||
|
|
||||||
exp = datetime.datetime.now(tz=tz_util.utc) - datetime.timedelta(1)
|
exp = datetime.datetime.now(tz=tz_util.utc) - datetime.timedelta(1)
|
||||||
result = tokens.update_one({'token': token},
|
result = tokens.update_one({'token': token},
|
||||||
{'$set': {'expire_time': exp}})
|
{'$set': {'expire_time': exp}})
|
||||||
self.assertEqual(1, result.modified_count)
|
self.assertEqual(1, result.modified_count)
|
||||||
|
|
||||||
|
# Do something restricted.
|
||||||
headers = {'Authorization': self.make_header(token)}
|
headers = {'Authorization': self.make_header(token)}
|
||||||
resp = self.client.get('/users/%s' % user_id,
|
resp = self.client.put('/users/%s' % user_id,
|
||||||
headers=headers)
|
headers=headers)
|
||||||
self.assertEqual(403, resp.status_code, resp.data)
|
self.assertEqual(403, resp.status_code, resp.data)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user