AB-testing for home project
Only allows access to the home project to user with role 'homeproject'.
This commit is contained in:
@@ -77,6 +77,7 @@ def create_home_project(user_id):
|
|||||||
|
|
||||||
|
|
||||||
@blueprint.route('/home-project')
|
@blueprint.route('/home-project')
|
||||||
|
@authorization.ab_testing(require_roles={u'homeproject'})
|
||||||
@authorization.require_login(require_roles={u'subscriber', u'demo'})
|
@authorization.require_login(require_roles={u'subscriber', u'demo'})
|
||||||
def home_project():
|
def home_project():
|
||||||
"""Fetches the home project, creating it if necessary.
|
"""Fetches the home project, creating it if necessary.
|
||||||
|
@@ -273,6 +273,37 @@ def require_login(require_roles=set(),
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def ab_testing(require_roles=set(),
|
||||||
|
require_all=False):
|
||||||
|
"""Decorator that raises a 404 when the user doesn't match the roles..
|
||||||
|
|
||||||
|
:param require_roles: set of roles.
|
||||||
|
:param require_all:
|
||||||
|
When False (the default): if the user's roles have a
|
||||||
|
non-empty intersection with the given roles, access is granted.
|
||||||
|
When True: require the user to have all given roles before access is
|
||||||
|
granted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(require_roles, set):
|
||||||
|
raise TypeError('require_roles param should be a set, but is a %r' % type(require_roles))
|
||||||
|
|
||||||
|
if require_all and not require_roles:
|
||||||
|
raise ValueError('require_login(require_all=True) cannot be used with empty require_roles.')
|
||||||
|
|
||||||
|
def decorator(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
if not user_matches_roles(require_roles, require_all):
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def user_has_role(role, user=None):
|
def user_has_role(role, user=None):
|
||||||
"""Returns True iff the user is logged in and has the given role."""
|
"""Returns True iff the user is logged in and has the given role."""
|
||||||
|
|
||||||
|
@@ -141,7 +141,7 @@ class AbstractPillarTest(TestMinimal):
|
|||||||
|
|
||||||
return token_data
|
return token_data
|
||||||
|
|
||||||
def badger(self, user_email, role, action, srv_token=None):
|
def badger(self, user_email, roles, action, srv_token=None):
|
||||||
"""Creates a service account, and uses it to grant or revoke a role to the user.
|
"""Creates a service account, and uses it to grant or revoke a role to the user.
|
||||||
|
|
||||||
To skip creation of the service account, pass a srv_token.
|
To skip creation of the service account, pass a srv_token.
|
||||||
@@ -150,15 +150,19 @@ class AbstractPillarTest(TestMinimal):
|
|||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if isinstance(roles, str):
|
||||||
|
roles = set(roles)
|
||||||
|
|
||||||
# Create a service account if needed.
|
# Create a service account if needed.
|
||||||
if srv_token is None:
|
if srv_token is None:
|
||||||
from application.modules.service import create_service_account
|
from application.modules.service import create_service_account
|
||||||
with self.app.test_request_context():
|
with self.app.test_request_context():
|
||||||
_, srv_token_doc = create_service_account('service@example.com',
|
_, srv_token_doc = create_service_account('service@example.com',
|
||||||
{'badger'},
|
{'badger'},
|
||||||
{'badger': [role]})
|
{'badger': list(roles)})
|
||||||
srv_token = srv_token_doc['token']
|
srv_token = srv_token_doc['token']
|
||||||
|
|
||||||
|
for role in roles:
|
||||||
resp = self.client.post('/service/badger',
|
resp = self.client.post('/service/badger',
|
||||||
headers={'Authorization': self.make_header(srv_token),
|
headers={'Authorization': self.make_header(srv_token),
|
||||||
'Content-Type': 'application/json'},
|
'Content-Type': 'application/json'},
|
||||||
|
@@ -23,7 +23,7 @@ class HomeProjectTest(AbstractPillarTest):
|
|||||||
|
|
||||||
def _create_user_with_token(self, roles, token, user_id='cafef00df00df00df00df00d'):
|
def _create_user_with_token(self, roles, token, user_id='cafef00df00df00df00df00d'):
|
||||||
"""Creates a user directly in MongoDB, so that it doesn't trigger any Eve hooks."""
|
"""Creates a user directly in MongoDB, so that it doesn't trigger any Eve hooks."""
|
||||||
user_id = self.create_user(roles=roles, user_id=user_id)
|
user_id = self.create_user(roles=roles.union({u'homeproject'}), user_id=user_id)
|
||||||
self.create_valid_auth_token(user_id, token)
|
self.create_valid_auth_token(user_id, token)
|
||||||
return user_id
|
return user_id
|
||||||
|
|
||||||
@@ -47,7 +47,9 @@ class HomeProjectTest(AbstractPillarTest):
|
|||||||
|
|
||||||
# Test availability at end-point
|
# Test availability at end-point
|
||||||
resp = self.client.get(endpoint)
|
resp = self.client.get(endpoint)
|
||||||
self.assertEqual(403, resp.status_code)
|
# While we're still AB-testing, unauthenticated users should get a 404.
|
||||||
|
# When that's over, it should result in a 403.
|
||||||
|
self.assertEqual(404, resp.status_code)
|
||||||
|
|
||||||
resp = self.client.get(endpoint, headers={'Authorization': self.make_header('token')})
|
resp = self.client.get(endpoint, headers={'Authorization': self.make_header('token')})
|
||||||
self.assertEqual(200, resp.status_code)
|
self.assertEqual(200, resp.status_code)
|
||||||
@@ -63,8 +65,8 @@ class HomeProjectTest(AbstractPillarTest):
|
|||||||
resp = self.client.get('/users/me', headers={'Authorization': self.make_header('token')})
|
resp = self.client.get('/users/me', headers={'Authorization': self.make_header('token')})
|
||||||
self.assertEqual(200, resp.status_code, resp)
|
self.assertEqual(200, resp.status_code, resp)
|
||||||
|
|
||||||
# Grant subscriber role, and fetch the home project.
|
# Grant subscriber and homeproject roles, and fetch the home project.
|
||||||
self.badger(TEST_EMAIL_ADDRESS, 'subscriber', 'grant')
|
self.badger(TEST_EMAIL_ADDRESS, {'subscriber', 'homeproject'}, 'grant')
|
||||||
|
|
||||||
resp = self.client.get('/bcloud/home-project',
|
resp = self.client.get('/bcloud/home-project',
|
||||||
headers={'Authorization': self.make_header('token')})
|
headers={'Authorization': self.make_header('token')})
|
||||||
@@ -73,6 +75,28 @@ class HomeProjectTest(AbstractPillarTest):
|
|||||||
json_proj = json.loads(resp.data)
|
json_proj = json.loads(resp.data)
|
||||||
self.assertEqual('home', json_proj['category'])
|
self.assertEqual('home', json_proj['category'])
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_home_project_ab_testing(self):
|
||||||
|
self.mock_blenderid_validate_happy()
|
||||||
|
resp = self.client.get('/users/me', headers={'Authorization': self.make_header('token')})
|
||||||
|
self.assertEqual(200, resp.status_code, resp)
|
||||||
|
|
||||||
|
# Grant subscriber and but NOT homeproject role, and fetch the home project.
|
||||||
|
self.badger(TEST_EMAIL_ADDRESS, {'subscriber'}, 'grant')
|
||||||
|
|
||||||
|
resp = self.client.get('/bcloud/home-project',
|
||||||
|
headers={'Authorization': self.make_header('token')})
|
||||||
|
self.assertEqual(404, resp.status_code)
|
||||||
|
|
||||||
|
resp = self.client.get('/users/me',
|
||||||
|
headers={'Authorization': self.make_header('token')})
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
me = json.loads(resp.data)
|
||||||
|
|
||||||
|
with self.app.test_request_context():
|
||||||
|
from application.modules.blender_cloud import home_project
|
||||||
|
self.assertFalse(home_project.has_home_project(me['_id']))
|
||||||
|
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_autocreate_home_project_with_demo_role(self):
|
def test_autocreate_home_project_with_demo_role(self):
|
||||||
# Implicitly create user by token validation.
|
# Implicitly create user by token validation.
|
||||||
@@ -80,8 +104,8 @@ class HomeProjectTest(AbstractPillarTest):
|
|||||||
resp = self.client.get('/users/me', headers={'Authorization': self.make_header('token')})
|
resp = self.client.get('/users/me', headers={'Authorization': self.make_header('token')})
|
||||||
self.assertEqual(200, resp.status_code, resp)
|
self.assertEqual(200, resp.status_code, resp)
|
||||||
|
|
||||||
# Grant demo role, which should allow creation of the home project.
|
# Grant demo and homeproject role, which should allow creation of the home project.
|
||||||
self.badger(TEST_EMAIL_ADDRESS, 'demo', 'grant')
|
self.badger(TEST_EMAIL_ADDRESS, {'demo', 'homeproject'}, 'grant')
|
||||||
|
|
||||||
resp = self.client.get('/bcloud/home-project',
|
resp = self.client.get('/bcloud/home-project',
|
||||||
headers={'Authorization': self.make_header('token')})
|
headers={'Authorization': self.make_header('token')})
|
||||||
@@ -97,8 +121,8 @@ class HomeProjectTest(AbstractPillarTest):
|
|||||||
resp = self.client.get('/users/me', headers={'Authorization': self.make_header('token')})
|
resp = self.client.get('/users/me', headers={'Authorization': self.make_header('token')})
|
||||||
self.assertEqual(200, resp.status_code, resp)
|
self.assertEqual(200, resp.status_code, resp)
|
||||||
|
|
||||||
# Grant demo role, which should NOT allow creation fo the home project.
|
# Grant succubus role, which should NOT allow creation fo the home project.
|
||||||
self.badger(TEST_EMAIL_ADDRESS, 'succubus', 'grant')
|
self.badger(TEST_EMAIL_ADDRESS, {'succubus', 'homeproject'}, 'grant')
|
||||||
|
|
||||||
resp = self.client.get('/bcloud/home-project',
|
resp = self.client.get('/bcloud/home-project',
|
||||||
headers={'Authorization': self.make_header('token')})
|
headers={'Authorization': self.make_header('token')})
|
||||||
@@ -135,7 +159,7 @@ class HomeProjectTest(AbstractPillarTest):
|
|||||||
self.assertEqual(200, resp.status_code, resp)
|
self.assertEqual(200, resp.status_code, resp)
|
||||||
|
|
||||||
# Grant subscriber role, and fetch the home project.
|
# Grant subscriber role, and fetch the home project.
|
||||||
self.badger(TEST_EMAIL_ADDRESS, 'subscriber', 'grant')
|
self.badger(TEST_EMAIL_ADDRESS, {'subscriber', 'homeproject'}, 'grant')
|
||||||
|
|
||||||
resp = self.client.get('/bcloud/home-project',
|
resp = self.client.get('/bcloud/home-project',
|
||||||
query_string={'projection': json.dumps(
|
query_string={'projection': json.dumps(
|
||||||
@@ -160,7 +184,7 @@ class HomeProjectTest(AbstractPillarTest):
|
|||||||
self.assertEqual(200, resp.status_code, resp)
|
self.assertEqual(200, resp.status_code, resp)
|
||||||
|
|
||||||
# Grant subscriber role, and fetch the home project.
|
# Grant subscriber role, and fetch the home project.
|
||||||
self.badger(TEST_EMAIL_ADDRESS, 'subscriber', 'grant')
|
self.badger(TEST_EMAIL_ADDRESS, {'subscriber', 'homeproject'}, 'grant')
|
||||||
|
|
||||||
resp = self.client.get('/bcloud/home-project',
|
resp = self.client.get('/bcloud/home-project',
|
||||||
headers={'Authorization': self.make_header('token')})
|
headers={'Authorization': self.make_header('token')})
|
||||||
|
Reference in New Issue
Block a user