def update_subscription(): """Updates the subscription status of the current user. Returns an empty HTTP response. """ import pprint from pillar.api import blender_id, service from pillar.api.utils import authentication my_log: logging.Logger = log.getChild('update_subscription') user_id = authentication.current_user_id() bid_user = blender_id.fetch_blenderid_user() if not bid_user: my_log.warning( 'Logged in user %s has no BlenderID account! ' 'Unable to update subscription status.', user_id) return '', 204 # Use the Blender ID email address to check with the store. At least that reduces the # number of email addresses that could be out of sync to two (rather than three when we # use the email address from our local database). try: email = bid_user['email'] except KeyError: my_log.error( 'Blender ID response did not include an email address, ' 'unable to update subscription status: %s', pprint.pformat(bid_user, compact=True)) return 'Internal error', 500 store_user = fetch_subscription_info(email) or {} # Handle the role changes via the badger service functionality. grant_subscriber = store_user.get('cloud_access', 0) == 1 grant_demo = bid_user.get('roles', {}).get('cloud_demo', False) is_subscriber = authorization.user_has_role('subscriber') is_demo = authorization.user_has_role('demo') if grant_subscriber != is_subscriber: action = 'grant' if grant_subscriber else 'revoke' my_log.info('%sing subscriber role to user %s (Blender ID email %s)', action, user_id, email) service.do_badger(action, role='subscriber', user_id=user_id) else: my_log.debug('Not changing subscriber role, grant=%r and is=%s', grant_subscriber, is_subscriber) if grant_demo != is_demo: action = 'grant' if grant_demo else 'revoke' my_log.info('%sing demo role to user %s (Blender ID email %s)', action, user_id, email) service.do_badger(action, role='demo', user_id=user_id) else: my_log.debug('Not changing demo role, grant=%r and is=%s', grant_demo, is_demo) return '', 204
def before_inserting_projects(items): """Strip unwanted properties, that will be assigned after creation. Also, verify permission to create a project (check quota, check role). :param items: List of project docs that have been inserted (normally one) """ # Allow admin users to do whatever they want. if user_has_role('admin'): return for item in items: item.pop('url', None)
def edit_comment(user_id, node_id, patch): """Edits a single comment. Doesn't do permission checking; users are allowed to edit their own comment, and this is not something you want to revoke anyway. Admins can edit all comments. """ # Find the node. We need to fetch some more info than we use here, so that # we can pass this stuff to Eve's patch_internal; that way the validation & # authorisation system has enough info to work. nodes_coll = current_app.data.driver.db['nodes'] projection = {'user': 1, 'project': 1, 'node_type': 1} node = nodes_coll.find_one(node_id, projection=projection) if node is None: log.warning('User %s wanted to patch non-existing node %s' % (user_id, node_id)) raise wz_exceptions.NotFound('Node %s not found' % node_id) if node['user'] != user_id and not authorization.user_has_role('admin'): raise wz_exceptions.Forbidden('You can only edit your own comments.') # Use Eve to PATCH this node, as that also updates the etag. r, _, _, status = current_app.patch_internal('nodes', {'properties.content': patch['content'], 'project': node['project'], 'user': node['user'], 'node_type': node['node_type']}, concurrency_check=False, _id=node_id) if status != 200: log.error('Error %i editing comment %s for user %s: %s', status, node_id, user_id, r) raise wz_exceptions.InternalServerError('Internal error %i from Eve' % status) else: log.info('User %s edited comment %s', user_id, node_id) # Fetch the new content, so the client can show these without querying again. node = nodes_coll.find_one(node_id, projection={ 'properties.content': 1, 'properties._content_html': 1, }) return status, node
def test_user_has_role(self): from pillar.api.utils.authorization import user_has_role def make_user(roles): return self.create_user_object(ObjectId(), roles=roles) with self.app.test_request_context(): self.assertTrue(user_has_role('subscriber', make_user(['aap', 'noot', 'subscriber']))) self.assertTrue(user_has_role('subscriber', make_user(['aap', 'subscriber']))) self.assertFalse(user_has_role('admin', make_user(['aap', 'noot', 'subscriber']))) self.assertFalse(user_has_role('admin', make_user([]))) self.assertFalse(user_has_role('admin', make_user(None))) self.assertFalse(user_has_role('admin', None))
def protect_sensitive_fields(document, original): """When not logged in as admin, prevents update to certain fields.""" # Allow admin users to do whatever they want. if user_has_role('admin'): return def revert(name): if name not in original: try: del document[name] except KeyError: pass return document[name] = original[name] revert('status') revert('category') revert('user') if 'url' in original: revert('url')
def project_manage_users(): """Manage users of a project. In this initial implementation, we handle addition and removal of a user to the admin group of a project. No changes are done on the project itself. """ from pillar.api.utils import str2id projects_collection = current_app.data.driver.db['projects'] users_collection = current_app.data.driver.db['users'] # TODO: check if user is admin of the project before anything if request.method == 'GET': project_id = request.args['project_id'] project = projects_collection.find_one({'_id': ObjectId(project_id)}) admin_group_id = project['permissions']['groups'][0]['group'] users = users_collection.find({'groups': { '$in': [admin_group_id] }}, { 'username': 1, 'email': 1, 'full_name': 1 }) return jsonify({'_status': 'OK', '_items': list(users)}) # The request is not a form, since it comes from the API sdk data = json.loads(request.data) project_id = str2id(data['project_id']) target_user_id = str2id(data['user_id']) action = data['action'] current_user_id = current_user.user_id project = projects_collection.find_one({'_id': project_id}) # Check if the current_user is owner of the project, or removing themselves. if not authorization.user_has_role('admin'): remove_self = target_user_id == current_user_id and action == 'remove' if project['user'] != current_user_id and not remove_self: utils.abort_with_error(403) admin_group = utils.get_admin_group(project) # Get the user and add the admin group to it if action == 'add': operation = '$addToSet' log.info( 'project_manage_users: Adding user %s to admin group of project %s', target_user_id, project_id) elif action == 'remove': log.info( 'project_manage_users: Removing user %s from admin group of project %s', target_user_id, project_id) operation = '$pull' else: log.warning( 'project_manage_users: Unsupported action %r called by user %s', action, current_user_id) raise wz_exceptions.UnprocessableEntity() users_collection.update({'_id': target_user_id}, {operation: { 'groups': admin_group['_id'] }}) user = users_collection.find_one({'_id': target_user_id}, { 'username': 1, 'email': 1, 'full_name': 1 }) if not user: return jsonify({'_status': 'ERROR'}), 404 user['_status'] = 'OK' return jsonify(user)
def process_file(bucket: Bucket, file_id: typing.Union[str, ObjectId], local_file: tempfile._TemporaryFileWrapper): """Process the file by creating thumbnails, sending to Zencoder, etc. :param file_id: '_id' key of the file :param local_file: locally stored file, or None if no local processing is needed. """ file_id = ObjectId(file_id) # Fetch the src_file document from MongoDB. files = current_app.data.driver.db['files'] src_file = files.find_one(file_id) if not src_file: log.warning('process_file(%s): no such file document found, ignoring.') return src_file = utils.remove_private_keys(src_file) # Update the 'format' field from the content type. # TODO: overrule the content type based on file extention & magic numbers. mime_category, src_file['format'] = src_file['content_type'].split('/', 1) # Prevent video handling for non-admins. if not user_has_role('admin') and mime_category == 'video': if src_file['format'].startswith('x-'): xified = src_file['format'] else: xified = 'x-' + src_file['format'] src_file['content_type'] = 'application/%s' % xified mime_category = 'application' log.info('Not processing video file %s for non-admin user', file_id) # Run the required processor, based on the MIME category. processors: typing.Mapping[str, typing.Callable] = { 'image': _process_image, 'video': _process_video, } try: processor = processors[mime_category] except KeyError: log.info( "POSTed file %s was of type %r, which isn't " "thumbnailed/encoded.", file_id, mime_category) src_file['status'] = 'complete' else: log.debug('process_file(%s): marking file status as "processing"', file_id) src_file['status'] = 'processing' update_file_doc(file_id, status='processing') try: processor(bucket, file_id, local_file, src_file) except Exception: log.warning( 'process_file(%s): error when processing file, ' 'resetting status to ' '"queued_for_processing"', file_id, exc_info=True) update_file_doc(file_id, status='queued_for_processing') return # Update the original file with additional info, e.g. image resolution r, _, _, status = current_app.put_internal('files', src_file, _id=file_id) if status not in (200, 201): log.warning( 'process_file(%s): status %i when saving processed file ' 'info to MongoDB: %s', file_id, status, r)