Beispiel #1
0
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
Beispiel #2
0
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)
Beispiel #3
0
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
Beispiel #4
0
    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))
Beispiel #5
0
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')
Beispiel #6
0
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)
Beispiel #7
0
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)