Simplified permissions for projects.

Instead of the additional 'is_private' field, we now just use
the permission system and set/remove world GET permissions.

'is_private' is still kept for backward compatibility and possibly
easy querying for public projects, and is always set based on
world GET permissions.
This commit is contained in:
2016-05-06 10:00:17 +02:00
parent 758273edab
commit a2ce18196a
4 changed files with 192 additions and 37 deletions

View File

@@ -11,11 +11,8 @@ from application.utils import remove_private_keys, authorization, jsonify
from application.utils.gcs import GoogleCloudStorageBucket
from application.utils.authorization import user_has_role, check_permissions
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.post import node_type_post
log = logging.getLogger(__name__)
blueprint = Blueprint('projects', __name__)
@@ -36,7 +33,23 @@ def before_inserting_projects(items):
item.pop('url', None)
def override_is_private_field(project, original):
"""Override the 'is_private' property from the world permissions.
:param project: the project, which will be updated
"""
world_perms = project['permissions'].get('world', [])
project['is_private'] = 'GET' not in world_perms
def before_inserting_override_is_private_field(projects):
for project in projects:
override_is_private_field(project, None)
def before_edit_check_permissions(document, original):
# Allow admin users to do whatever they want.
# TODO: possibly move this into the check_permissions function.
if user_has_role(u'admin'):
@@ -108,7 +121,7 @@ def after_inserting_project(project, db_user):
return abort_with_error(status)
admin_group_id = result['_id']
log.info('Created admin group %s for project %s', admin_group_id, project_id)
log.debug('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)
@@ -121,8 +134,10 @@ def after_inserting_project(project, db_user):
log.debug('Made user %s member of group %s', user_id, admin_group_id)
# Assign the group to the project with admin rights
is_admin = authorization.is_admin(db_user)
world_permissions = ['GET'] if is_admin else []
permissions = {
'world': ['GET'],
'world': world_permissions,
'users': [],
'groups': [
{'group': admin_group_id,
@@ -143,7 +158,7 @@ def after_inserting_project(project, db_user):
with_permissions(node_type_comment)]
# Allow admin users to use whatever url they want.
if not user_has_role(u'admin') or not project.get('url'):
if not is_admin or not project.get('url'):
project['url'] = "p-{!s}".format(project_id)
# Initialize storage page (defaults to GCS)
@@ -192,10 +207,10 @@ def _create_new_project(project_name, user_id, overrides):
return abort_with_error(status)
project.update(result)
# Now re-fetch the etag, as both the initial document and the returned
# result do not contain the same etag as the database.
document = current_app.data.driver.db['projects'].find_one(project['_id'],
projection={'_etag': 1})
# Now re-fetch the project, as both the initial document and the returned
# result do not contain the same etag as the database. This also updates
# other fields set by hooks.
document = current_app.data.driver.db['projects'].find_one(project['_id'])
project.update(document)
log.info('Created project %s for user %s', project['_id'], user_id)
@@ -289,11 +304,14 @@ def abort_with_error(status):
def setup_app(app, url_prefix):
app.on_replace_projects += override_is_private_field
app.on_replace_projects += before_edit_check_permissions
app.on_replace_projects += protect_sensitive_fields
app.on_update_projects += override_is_private_field
app.on_update_projects += before_edit_check_permissions
app.on_update_projects += protect_sensitive_fields
app.on_delete_item_projects += before_delete_project
app.on_insert_projects += before_inserting_override_is_private_field
app.on_insert_projects += before_inserting_projects
app.on_inserted_projects += after_inserting_projects
app.register_blueprint(blueprint, url_prefix=url_prefix)

View File

@@ -40,10 +40,15 @@ class PillarJSONEncoder(json.JSONEncoder):
return json.JSONEncoder.default(self, obj)
def dumps(mongo_doc, **kwargs):
"""json.dumps() for MongoDB documents."""
return json.dumps(mongo_doc, cls=PillarJSONEncoder, **kwargs)
def jsonify(mongo_doc, status=200, headers=None):
"""JSonifies a Mongo document into a Flask response object."""
return current_app.response_class(json.dumps(mongo_doc, cls=PillarJSONEncoder),
return current_app.response_class(dumps(mongo_doc),
mimetype='application/json',
status=status,
headers=headers)

View File

@@ -112,13 +112,19 @@ def require_login(require_roles=set()):
return decorator
def user_has_role(role):
def user_has_role(role, user=None):
"""Returns True iff the user is logged in and has the given role."""
current_user = g.get('current_user')
if current_user is None:
if user is None:
user = g.get('current_user')
if user is None:
return False
return role in current_user['roles']
return role in user['roles']
def is_admin(user):
"""Returns True iff the given user has the admin role."""
return user_has_role(u'admin', user)