Home project: create it when user tries to GET it.
This commit is contained in:
parent
3980133100
commit
b4faf2245e
@ -1,5 +1,6 @@
|
||||
|
||||
def setup_app(app, url_prefix):
|
||||
from . import texture_libs
|
||||
from . import texture_libs, home_project
|
||||
|
||||
texture_libs.setup_app(app, url_prefix=url_prefix)
|
||||
home_project.setup_app(app, url_prefix=url_prefix)
|
||||
|
92
pillar/application/modules/blender_cloud/home_project.py
Normal file
92
pillar/application/modules/blender_cloud/home_project.py
Normal file
@ -0,0 +1,92 @@
|
||||
import logging
|
||||
|
||||
from bson import ObjectId
|
||||
from eve.methods.put import put_internal
|
||||
from eve.methods.get import get
|
||||
from flask import Blueprint, g, current_app
|
||||
from werkzeug import exceptions as wz_exceptions
|
||||
|
||||
from application.modules import projects
|
||||
from application import utils
|
||||
from application.utils import authentication, authorization
|
||||
|
||||
blueprint = Blueprint('blender_cloud.home_project', __name__)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Users with any of these roles will get a home project.
|
||||
HOME_PROJECT_USERS = {u'subscriber', u'demo'}
|
||||
|
||||
|
||||
def create_home_project(user_id):
|
||||
"""Creates a home project for the given user."""
|
||||
|
||||
log.info('Creating home project for user %s', user_id)
|
||||
project = projects.create_new_project(project_name='Home',
|
||||
user_id=ObjectId(user_id),
|
||||
overrides={'category': 'home'})
|
||||
|
||||
# Re-validate the authentication token, so that the put_internal call sees the
|
||||
# new group created for the project.
|
||||
authentication.validate_token()
|
||||
|
||||
# There are a few things in the on_insert_projects hook we need to adjust.
|
||||
|
||||
# Ensure that the project is private, even for admins.
|
||||
project['permissions']['world'] = []
|
||||
|
||||
# Set up the correct node types. No need to set permissions for them,
|
||||
# as the inherited project permissions are fine.
|
||||
from manage_extra.node_types.group import node_type_group
|
||||
from manage_extra.node_types.asset import node_type_asset
|
||||
from manage_extra.node_types.text import node_type_text
|
||||
|
||||
project['node_types'] = [
|
||||
node_type_group,
|
||||
node_type_asset,
|
||||
node_type_text,
|
||||
]
|
||||
|
||||
result, _, _, status = put_internal('projects', utils.remove_private_keys(project),
|
||||
_id=project['_id'])
|
||||
if status != 200:
|
||||
log.error('Unable to update home project %s for user %s: %s',
|
||||
project['_id'], user_id, result)
|
||||
raise wz_exceptions.InternalServerError('Unable to update home project')
|
||||
project.update(result)
|
||||
|
||||
return project
|
||||
|
||||
|
||||
@blueprint.route('/home-project')
|
||||
@authorization.require_login(require_roles={u'subscriber', u'demo'})
|
||||
def home_project():
|
||||
user_id = g.current_user['user_id']
|
||||
|
||||
roles = g.current_user.get('roles', ())
|
||||
log.debug('Possibly creating home project for user %s with roles %s', user_id, roles)
|
||||
if not HOME_PROJECT_USERS.intersection(roles):
|
||||
log.debug('User %s is not a subscriber, not creating home project.', user_id)
|
||||
return 'No home project', 404
|
||||
|
||||
resp, _, _, status, _ = get('projects', category=u'home', user=user_id)
|
||||
if status != 200:
|
||||
return utils.jsonify(resp), status
|
||||
|
||||
if resp['_items']:
|
||||
project = resp['_items'][0]
|
||||
else:
|
||||
log.debug('Home project for user %s not found', user_id)
|
||||
project = create_home_project(user_id)
|
||||
|
||||
return utils.jsonify(project), status
|
||||
|
||||
|
||||
def has_home_project(user_id):
|
||||
"""Returns True iff the user has a home project."""
|
||||
|
||||
proj_coll = current_app.data.driver.db['projects']
|
||||
return proj_coll.count({'user': user_id, 'category': 'home'}) > 0
|
||||
|
||||
|
||||
def setup_app(app, url_prefix):
|
||||
app.register_blueprint(blueprint, url_prefix=url_prefix)
|
@ -194,7 +194,7 @@ def after_inserting_project(project, db_user):
|
||||
abort_with_error(500)
|
||||
|
||||
|
||||
def _create_new_project(project_name, user_id, overrides):
|
||||
def create_new_project(project_name, user_id, overrides):
|
||||
"""Creates a new project owned by the given user."""
|
||||
|
||||
log.info('Creating new project "%s" for user %s', project_name, user_id)
|
||||
@ -242,7 +242,7 @@ def create_project(overrides=None):
|
||||
project_name = request.form['project_name']
|
||||
user_id = g.current_user['user_id']
|
||||
|
||||
project = _create_new_project(project_name, user_id, overrides)
|
||||
project = create_new_project(project_name, user_id, overrides)
|
||||
|
||||
# Return the project in the response.
|
||||
return jsonify(project, status=201, headers={'Location': '/projects/%s' % project['_id']})
|
||||
@ -306,6 +306,7 @@ def project_manage_users():
|
||||
user = users_collection.find_one({'_id': target_user_id},
|
||||
{'username': 1, 'email': 1,
|
||||
'full_name': 1})
|
||||
|
||||
user['_status'] = 'OK'
|
||||
return jsonify(user)
|
||||
|
||||
|
@ -7,7 +7,7 @@ from flask import Blueprint, current_app, g, request
|
||||
from werkzeug import exceptions as wz_exceptions
|
||||
|
||||
from application.utils import authorization
|
||||
from application.modules import local_auth
|
||||
from application.modules import local_auth, users
|
||||
|
||||
blueprint = Blueprint('service', __name__)
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -4,7 +4,8 @@ import json
|
||||
import logging
|
||||
import urllib
|
||||
|
||||
from flask import g, current_app, Blueprint, make_response
|
||||
from flask import g, current_app, Blueprint
|
||||
|
||||
from werkzeug.exceptions import Forbidden
|
||||
from eve.utils import parse_request
|
||||
from eve.methods.get import get
|
||||
|
28
pillar/manage_extra/node_types/text.py
Normal file
28
pillar/manage_extra/node_types/text.py
Normal file
@ -0,0 +1,28 @@
|
||||
node_type_text = {
|
||||
'name': 'text',
|
||||
'description': 'Text',
|
||||
'parent': ['group', 'project'],
|
||||
'dyn_schema': {
|
||||
'content': {
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'minlength': 3,
|
||||
'maxlength': 90000,
|
||||
},
|
||||
'shared_slug': {
|
||||
'type': 'string',
|
||||
'required': False,
|
||||
},
|
||||
'syntax': { # for syntax highlighting
|
||||
'type': 'string',
|
||||
'required': False,
|
||||
},
|
||||
'node_expires': {
|
||||
'type': 'datetime',
|
||||
'required': False,
|
||||
},
|
||||
},
|
||||
'form_schema': {
|
||||
'shared_slug': {'visible': False},
|
||||
}
|
||||
}
|
@ -569,7 +569,8 @@ projects_schema = {
|
||||
'film',
|
||||
'assets',
|
||||
'software',
|
||||
'game'
|
||||
'game',
|
||||
'home',
|
||||
],
|
||||
'required': True,
|
||||
},
|
||||
|
@ -106,7 +106,7 @@ class AbstractPillarTest(TestMinimal):
|
||||
|
||||
return found['_id'], found
|
||||
|
||||
def create_user(self, user_id='cafef00dc379cf10c4aaceaf', roles=('subscriber', )):
|
||||
def create_user(self, user_id='cafef00dc379cf10c4aaceaf', roles=('subscriber',)):
|
||||
from application.utils.authentication import make_unique_username
|
||||
|
||||
with self.app.test_request_context():
|
||||
@ -141,6 +141,34 @@ class AbstractPillarTest(TestMinimal):
|
||||
|
||||
return token_data
|
||||
|
||||
def badger(self, user_email, role, action, srv_token=None):
|
||||
"""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.
|
||||
|
||||
:returns: the authentication token of the created service account.
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
# Create a service account if needed.
|
||||
if srv_token is None:
|
||||
from application.modules.service import create_service_account
|
||||
with self.app.test_request_context():
|
||||
_, srv_token_doc = create_service_account('service@example.com',
|
||||
{'badger'},
|
||||
{'badger': [role]})
|
||||
srv_token = srv_token_doc['token']
|
||||
|
||||
resp = self.client.post('/service/badger',
|
||||
headers={'Authorization': self.make_header(srv_token),
|
||||
'Content-Type': 'application/json'},
|
||||
data=json.dumps({'action': action,
|
||||
'role': role,
|
||||
'user_email': user_email}))
|
||||
self.assertEqual(204, resp.status_code, resp.data)
|
||||
|
||||
return srv_token
|
||||
|
||||
def mock_blenderid_validate_unhappy(self):
|
||||
"""Sets up Responses to mock unhappy validation flow."""
|
||||
|
||||
|
108
tests/test_bcloud_home_project.py
Normal file
108
tests/test_bcloud_home_project.py
Normal file
@ -0,0 +1,108 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
"""Unit tests for the Blender Cloud home project module."""
|
||||
|
||||
import functools
|
||||
import json
|
||||
import logging
|
||||
import urllib
|
||||
|
||||
import responses
|
||||
from bson import ObjectId
|
||||
from flask import g, url_for
|
||||
|
||||
from common_test_class import AbstractPillarTest, TEST_EMAIL_ADDRESS
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HomeProjectTest(AbstractPillarTest):
|
||||
|
||||
def setUp(self, **kwargs):
|
||||
AbstractPillarTest.setUp(self, **kwargs)
|
||||
self.create_standard_groups()
|
||||
|
||||
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."""
|
||||
user_id = self.create_user(roles=roles, user_id=user_id)
|
||||
self.create_valid_auth_token(user_id, token)
|
||||
return user_id
|
||||
|
||||
def test_create_home_project(self):
|
||||
from application.modules.blender_cloud import home_project
|
||||
from application.utils.authentication import validate_token
|
||||
|
||||
user_id = self._create_user_with_token(roles={u'subscriber'}, token='token')
|
||||
|
||||
# Test home project creation
|
||||
with self.app.test_request_context(headers={'Authorization': self.make_header('token')}):
|
||||
validate_token()
|
||||
|
||||
proj = home_project.create_home_project(user_id)
|
||||
self.assertEqual('home', proj['category'])
|
||||
self.assertEqual({u'text', u'group', u'asset'},
|
||||
set(nt['name'] for nt in proj['node_types']))
|
||||
|
||||
endpoint = url_for('blender_cloud.home_project.home_project')
|
||||
db_proj = self.app.data.driver.db['projects'].find_one(proj['_id'])
|
||||
|
||||
# Test availability at end-point
|
||||
resp = self.client.get(endpoint)
|
||||
self.assertEqual(403, resp.status_code)
|
||||
|
||||
resp = self.client.get(endpoint, headers={'Authorization': self.make_header('token')})
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
json_proj = json.loads(resp.data)
|
||||
self.assertEqual(ObjectId(json_proj['_id']), proj['_id'])
|
||||
self.assertEqual(json_proj['_etag'], db_proj['_etag'])
|
||||
|
||||
@responses.activate
|
||||
def test_autocreate_home_project_after_getting_subscriber_role(self):
|
||||
from application.modules.blender_cloud import home_project
|
||||
|
||||
# Implicitly create user by token validation.
|
||||
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 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(200, resp.status_code)
|
||||
|
||||
json_proj = json.loads(resp.data)
|
||||
self.assertEqual('home', json_proj['category'])
|
||||
|
||||
@responses.activate
|
||||
def test_autocreate_home_project_after_getting_demo_role(self):
|
||||
# Implicitly create user by token validation.
|
||||
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 demo role, which should also should allow creation fo the home project.
|
||||
self.badger(TEST_EMAIL_ADDRESS, 'demo', 'grant')
|
||||
|
||||
resp = self.client.get('/bcloud/home-project',
|
||||
headers={'Authorization': self.make_header('token')})
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
json_proj = json.loads(resp.data)
|
||||
self.assertEqual('home', json_proj['category'])
|
||||
|
||||
def test_has_home_project(self):
|
||||
from application.modules.blender_cloud import home_project
|
||||
from application.utils.authentication import validate_token
|
||||
|
||||
user_id = self._create_user_with_token(roles={u'subscriber'}, token='token')
|
||||
|
||||
# Test home project creation
|
||||
with self.app.test_request_context(headers={'Authorization': self.make_header('token')}):
|
||||
validate_token()
|
||||
|
||||
self.assertFalse(home_project.has_home_project(user_id))
|
||||
home_project.create_home_project(user_id)
|
||||
self.assertTrue(home_project.has_home_project(user_id))
|
@ -138,13 +138,13 @@ class ProjectCreationTest(AbstractProjectTest):
|
||||
headers={'Authorization': self.make_header('token-a')})
|
||||
self.assertEqual(200, resp.status_code)
|
||||
proj_list = json.loads(resp.data)
|
||||
self.assertEqual([u'Prøject A'], [p['name'] for p in proj_list['_items']])
|
||||
self.assertEqual({u'Prøject A'}, {p['name'] for p in proj_list['_items']})
|
||||
|
||||
resp = self.client.get('/projects',
|
||||
headers={'Authorization': self.make_header('token-b')})
|
||||
self.assertEqual(200, resp.status_code)
|
||||
proj_list = json.loads(resp.data)
|
||||
self.assertEqual([u'Prøject B'], [p['name'] for p in proj_list['_items']])
|
||||
self.assertEqual({u'Prøject B'}, {p['name'] for p in proj_list['_items']})
|
||||
|
||||
# No access to anything for user C, should result in empty list.
|
||||
self._create_user_with_token(roles={u'subscriber'}, token='token-c', user_id=12 * 'c')
|
||||
|
Loading…
x
Reference in New Issue
Block a user