Added /p/create entry point to create new projects.
This requires the user to be logged in. The project will be owned by that user.
This commit is contained in:
@@ -354,9 +354,6 @@ app.on_inserted_nodes += after_inserting_nodes
|
||||
app.on_fetched_item_projects += before_returning_item_permissions
|
||||
app.on_fetched_item_projects += project_node_type_has_method
|
||||
app.on_fetched_resource_projects += before_returning_resource_permissions
|
||||
# Projects hooks
|
||||
app.on_insert_projects += before_inserting_projects
|
||||
app.on_inserted_projects += after_inserting_projects
|
||||
|
||||
|
||||
def post_GET_user(request, payload):
|
||||
@@ -389,6 +386,8 @@ file_storage.setup_app(app, url_prefix='/storage')
|
||||
# The encoding module (receive notification and report progress)
|
||||
from modules.encoding import encoding
|
||||
from modules.blender_id import blender_id
|
||||
from modules import projects
|
||||
|
||||
app.register_blueprint(encoding, url_prefix='/encoding')
|
||||
app.register_blueprint(blender_id, url_prefix='/blender_id')
|
||||
projects.setup_app(app, url_prefix='/p')
|
||||
|
@@ -1,17 +1,22 @@
|
||||
import logging
|
||||
from flask import g
|
||||
from flask import abort
|
||||
from eve.methods.put import put_internal
|
||||
import json
|
||||
|
||||
from eve.methods.post import post_internal
|
||||
from application import app
|
||||
from application.utils import remove_private_keys
|
||||
from eve.methods.put import put_internal
|
||||
from eve.methods.patch import patch_internal
|
||||
from flask import g, Blueprint, request, abort, current_app
|
||||
|
||||
from application.utils import remove_private_keys, authorization, PillarJSONEncoder
|
||||
from application.utils.gcs import GoogleCloudStorageBucket
|
||||
from manage_extra.node_types.asset import node_type_asset
|
||||
from manage_extra.node_types.blog import node_type_blog
|
||||
from manage_extra.node_types.comment import node_type_comment
|
||||
from manage_extra.node_types.group import node_type_group
|
||||
from manage_extra.node_types.page import node_type_page
|
||||
from manage_extra.node_types.comment import node_type_comment
|
||||
from manage_extra.node_types.post import node_type_post
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
blueprint = Blueprint('projects', __name__)
|
||||
|
||||
|
||||
def before_inserting_projects(items):
|
||||
@@ -34,52 +39,131 @@ def after_inserting_projects(items):
|
||||
:param items: List of project docs that have been inserted (normally one)
|
||||
"""
|
||||
current_user = g.current_user
|
||||
users_collection = app.data.driver.db['users']
|
||||
user = users_collection.find_one({'_id': current_user['user_id']})
|
||||
users_collection = current_app.data.driver.db['users']
|
||||
user = users_collection.find_one(current_user['user_id'])
|
||||
|
||||
for item in items:
|
||||
# Create a project specific group (with name matching the project id)
|
||||
project_group = dict(name=str(item['_id']))
|
||||
group = post_internal('groups', project_group)
|
||||
# If Group creation failed, stop
|
||||
# TODO: undo project creation
|
||||
if group[3] != 201:
|
||||
abort(group[3])
|
||||
else:
|
||||
group = group[0]
|
||||
# Assign the current user to the group
|
||||
if 'groups' in user:
|
||||
user['groups'].append(group['_id'])
|
||||
else:
|
||||
user['groups'] = [group['_id']]
|
||||
put_internal('users', remove_private_keys(user), _id=user['_id'])
|
||||
# Assign the group to the project with admin rights
|
||||
permissions = dict(
|
||||
world=['GET'],
|
||||
users=[],
|
||||
groups=[
|
||||
dict(group=group['_id'],
|
||||
methods=['GET', 'PUT', 'POST'])
|
||||
]
|
||||
)
|
||||
# Assign permissions to the project itself, as well as to the node_types
|
||||
item['permissions'] = permissions
|
||||
node_type_asset['permissions'] = permissions
|
||||
node_type_group['permissions'] = permissions
|
||||
node_type_page['permissions'] = permissions
|
||||
node_type_comment['permissions'] = permissions
|
||||
# Assign the basic 'group', 'asset' and 'page' node_types
|
||||
item['node_types'] = [
|
||||
node_type_group,
|
||||
node_type_asset,
|
||||
node_type_page,
|
||||
node_type_comment]
|
||||
# TODO: Depending on user role or status, assign the url attribute
|
||||
# Initialize storage page (defaults to GCS)
|
||||
gcs_storage = GoogleCloudStorageBucket(str(item['_id']))
|
||||
after_inserting_project(item, user)
|
||||
|
||||
|
||||
def after_inserting_project(project, db_user):
|
||||
project_id = project['_id']
|
||||
user_id = db_user['_id']
|
||||
|
||||
# Create a project-specific admin group (with name matching the project id)
|
||||
result, _, _, status = post_internal('groups', {'name': str(project_id)})
|
||||
if status != 201:
|
||||
log.error('Unable to create admin group for new project %s: %s',
|
||||
project_id, result)
|
||||
return abort_with_error(status)
|
||||
|
||||
admin_group_id = result['_id']
|
||||
log.info('Created admin group %s for project %s', admin_group_id, project_id)
|
||||
|
||||
# Assign the current user to the group
|
||||
db_user.setdefault('groups', []).append(admin_group_id)
|
||||
|
||||
result, _, _, status = patch_internal('users', {'groups': db_user['groups']}, _id=user_id)
|
||||
if status != 200:
|
||||
log.error('Unable to add user %s as member of admin group %s for new project %s: %s',
|
||||
user_id, admin_group_id, project_id, result)
|
||||
return abort_with_error(status)
|
||||
log.debug('Made user %s member of group %s', user_id, admin_group_id)
|
||||
|
||||
# Assign the group to the project with admin rights
|
||||
permissions = {
|
||||
'world': ['GET'],
|
||||
'users': [],
|
||||
'groups': [
|
||||
{'group': admin_group_id,
|
||||
'methods': ['GET', 'PUT', 'POST']},
|
||||
]
|
||||
}
|
||||
|
||||
# Assign permissions to the project itself, as well as to the node_types
|
||||
project['permissions'] = permissions
|
||||
node_type_asset['permissions'] = permissions
|
||||
node_type_group['permissions'] = permissions
|
||||
node_type_page['permissions'] = permissions
|
||||
node_type_comment['permissions'] = permissions
|
||||
|
||||
# Assign the basic 'group', 'asset' and 'page' node_types
|
||||
project['node_types'] = [
|
||||
node_type_group,
|
||||
node_type_asset,
|
||||
node_type_page,
|
||||
node_type_comment]
|
||||
project['url'] = "p-{!s}".format(project_id)
|
||||
|
||||
# Initialize storage page (defaults to GCS)
|
||||
if current_app.config.get('TESTING'):
|
||||
log.warning('Not creating Google Cloud Storage bucket while running unit tests!')
|
||||
else:
|
||||
gcs_storage = GoogleCloudStorageBucket(str(project_id))
|
||||
if gcs_storage.bucket.exists():
|
||||
log.debug("Created CGS bucket {0}".format(item['_id']))
|
||||
# Assign a url based on the project id
|
||||
item['url'] = "p-{}".format(item['_id'])
|
||||
# Commit the changes
|
||||
put_internal('projects', remove_private_keys(item), _id=item['_id'])
|
||||
log.info('Created CGS instance for project %s', project_id)
|
||||
else:
|
||||
log.warning('Unable to create CGS instance for project %s', project_id)
|
||||
|
||||
# Commit the changes
|
||||
result, _, _, status = put_internal('projects', remove_private_keys(project), _id=project_id)
|
||||
if status != 200:
|
||||
log.warning('Unable to update project %s: %s', project_id, result)
|
||||
abort_with_error(status)
|
||||
|
||||
|
||||
@blueprint.route('/create', methods=['POST'])
|
||||
@authorization.require_login(require_roles={'admin', 'subscriber'})
|
||||
def create_project():
|
||||
"""Creates a new project."""
|
||||
|
||||
project_name = request.form['project_name']
|
||||
user_id = g.current_user['user_id']
|
||||
|
||||
log.info('Creating new project "%s" for user %s', project_name, user_id)
|
||||
|
||||
# Create the project itself, the rest will be done by the after-insert hook.
|
||||
project = {'description': '',
|
||||
'name': project_name,
|
||||
'node_types': [node_type_blog,
|
||||
node_type_post,
|
||||
node_type_comment],
|
||||
'status': 'published',
|
||||
'user': user_id,
|
||||
'is_private': True,
|
||||
'permissions': {},
|
||||
'url': '',
|
||||
'summary': '',
|
||||
'category': 'assets', # TODO: allow the user to choose this.
|
||||
}
|
||||
|
||||
result, _, _, status = post_internal('projects', project)
|
||||
if status != 201:
|
||||
log.error('Unable to create project "%s": %s', project_name, result)
|
||||
return abort_with_error(status)
|
||||
project.update(result)
|
||||
|
||||
project_id = result['_id']
|
||||
log.info('Created project %s for user %s', project_id, user_id)
|
||||
|
||||
# Return the project in the response.
|
||||
resp = current_app.response_class(json.dumps(project, cls=PillarJSONEncoder),
|
||||
mimetype='application/json',
|
||||
status=201,
|
||||
headers={'Location': '/projects/%s' % project_id})
|
||||
return resp
|
||||
|
||||
|
||||
def abort_with_error(status):
|
||||
"""Aborts with the given status, or 500 if the status doesn't indicate an error.
|
||||
|
||||
If the status is < 400, status 500 is used instead.
|
||||
"""
|
||||
|
||||
abort(status if status // 100 >= 4 else 500)
|
||||
|
||||
|
||||
def setup_app(app, url_prefix):
|
||||
app.on_insert_projects += before_inserting_projects
|
||||
app.on_inserted_projects += after_inserting_projects
|
||||
app.register_blueprint(blueprint, url_prefix=url_prefix)
|
||||
|
@@ -25,8 +25,6 @@ class PillarJSONEncoder(json.JSONEncoder):
|
||||
|
||||
def default(self, obj):
|
||||
if isinstance(obj, datetime.datetime):
|
||||
if obj.tzinfo is None:
|
||||
raise ValueError('All datetime.datetime objects should be timezone-aware.')
|
||||
return obj.strftime(RFC1123_DATE_FORMAT)
|
||||
|
||||
if isinstance(obj, bson.ObjectId):
|
||||
|
@@ -63,7 +63,8 @@ def validate_token():
|
||||
return False
|
||||
|
||||
g.current_user = {'user_id': db_user['_id'],
|
||||
'groups': db_user['groups']}
|
||||
'groups': db_user['groups'],
|
||||
'roles': set(db_user.get('roles', []))}
|
||||
|
||||
return True
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import functools
|
||||
|
||||
from flask import g
|
||||
from flask import request
|
||||
@@ -83,3 +84,28 @@ def check_permissions(resource, method, append_allowed_methods=False):
|
||||
return
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
def require_login(require_roles=set()):
|
||||
"""Decorator that enforces users to authenticate.
|
||||
|
||||
Optionally only allows access to users with a certain role./
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
current_user = g.get('current_user')
|
||||
|
||||
if current_user is None:
|
||||
log.warning('Unauthenticated acces to %s attempted.', func)
|
||||
abort(403)
|
||||
|
||||
if require_roles and not require_roles.intersection(set(current_user['roles'])):
|
||||
log.warning('User %s is authenticated, but does not have any required role %s to '
|
||||
'access %s', current_user['user_id'], require_roles, func)
|
||||
abort(403)
|
||||
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
@@ -560,28 +560,6 @@ projects_schema = {
|
||||
'embeddable': True
|
||||
},
|
||||
},
|
||||
'owners': {
|
||||
'type': 'dict',
|
||||
'schema': {
|
||||
'users': {
|
||||
'type': 'list',
|
||||
'schema': {
|
||||
'type': 'objectid',
|
||||
}
|
||||
},
|
||||
'groups': {
|
||||
'type': 'list',
|
||||
'schema': {
|
||||
'type': 'objectid',
|
||||
'data_relation': {
|
||||
'resource': 'groups',
|
||||
'field': '_id',
|
||||
'embeddable': True
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'status': {
|
||||
'type': 'string',
|
||||
'allowed': [
|
||||
|
Reference in New Issue
Block a user