Example #1
0
    def create_manager_doc(self,
                           service_account_id,
                           name,
                           description,
                           url=None):
        """Creates a new Flamenco manager and its owner group.

        Returns the MongoDB document.
        """

        from pillar.api.utils import str2id
        import bson

        # Determine the Object IDs beforehand, so that the manager can refer to the
        # group (by actual ID) and the group can mention the manager ID in the name.
        manager_id = bson.ObjectId()
        group_id = bson.ObjectId()

        # Create an owner group for this manager.
        group_doc = {
            '_id': group_id,
            'name': f'Owners of Flamenco Manager {manager_id}'
        }
        r, _, _, status = current_app.post_internal('groups', group_doc)
        if status != 201:
            self._log.error(
                'Error creating manager owner group; status should be 201, not %i: %s',
                status, r)
            raise ValueError(
                f'Unable to create Flamenco manager, status code {status}')

        # Create the manager.
        mngr_doc = {
            '_id': manager_id,
            'name': name,
            'description': description,
            'job_types': {
                'sleep': {
                    'vars': {}
                }
            },
            'service_account': str2id(service_account_id),
            'owner': group_id,
        }
        if url:
            mngr_doc['url'] = url
            self._log.info('Creating manager %r at %s', name, url)
        else:
            self._log.info('Creating manager %r', name)

        r, _, _, status = current_app.post_internal('flamenco_managers',
                                                    mngr_doc)
        if status != 201:
            self._log.error('Status should be 201, not %i: %s' % (status, r))
            raise ValueError(
                'Unable to create Flamenco manager, status code %i' % status)

        mngr_doc.update(r)
        return mngr_doc
Example #2
0
    def create_new_org(self,
                       name: str,
                       admin_uid: bson.ObjectId,
                       seat_count: int,
                       *,
                       org_roles: typing.Iterable[str] = None) -> dict:
        """Creates a new Organization.

        Returns the new organization document.
        """

        assert isinstance(admin_uid, bson.ObjectId)

        org_doc = {
            'name': name,
            'admin_uid': admin_uid,
            'seat_count': seat_count,
        }

        if org_roles:
            org_doc['org_roles'] = list(org_roles)

        r, _, _, status = current_app.post_internal('organizations', org_doc)
        if status != 201:
            self._log.error(
                'Error creating organization; status should be 201, not %i: %s',
                status, r)
            raise ValueError(
                f'Unable to create organization, status code {status}')

        org_doc.update(r)
        return org_doc
Example #3
0
def post_node_comment(parent_id: bson.ObjectId, markdown_msg: str,
                      attachments: dict):
    parent_node = find_node_or_raise(
        parent_id, 'User %s tried to update comment with bad parent_id %s',
        current_user.objectid, parent_id)

    is_reply = parent_node['node_type'] == 'comment'
    comment = dict(
        parent=parent_id,
        project=parent_node['project'],
        name='Comment',
        user=current_user.objectid,
        node_type='comment',
        properties=dict(
            content=markdown_msg,
            status='published',
            is_reply=is_reply,
            confidence=0,
            rating_positive=0,
            rating_negative=0,
            attachments=attachments,
        ),
        permissions=dict(
            users=[dict(user=current_user.objectid, methods=['PUT'])]))
    r, _, _, status = current_app.post_internal('nodes', comment)

    if status != 201:
        log.warning('Unable to post comment on %s as %s: %s', parent_id,
                    current_user.objectid, r)
        raise wz_exceptions.InternalServerError('Unable to create comment')

    comment_do = get_comment(parent_id, r['_id'])

    return jsonify_data_object(comment_do), 201
Example #4
0
def setup_db(admin_email):
    """Setup the database
    - Create admin, subscriber and demo Group collection
    - Create admin user (must use valid blender-id credentials)
    - Create one project
    """

    # Create default groups
    groups_list = []
    for group in ['admin', 'subscriber', 'demo']:
        g = {'name': group}
        g = current_app.post_internal('groups', g)
        groups_list.append(g[0]['_id'])
        print("Creating group {0}".format(group))

    # Create admin user
    user = {
        'username': admin_email,
        'groups': groups_list,
        'roles': ['admin', 'subscriber', 'demo'],
        'settings': {
            'email_communications': 1
        },
        'auth': [],
        'full_name': admin_email,
        'email': admin_email
    }
    result, _, _, status = current_app.post_internal('users', user)
    if status != 201:
        raise SystemExit('Error creating user {}: {}'.format(
            admin_email, result))
    user.update(result)
    print("Created user {0}".format(user['_id']))

    # Create a default project by faking a POST request.
    with current_app.test_request_context(
            data={'project_name': 'Default Project'}):
        from flask import g
        from pillar.auth import UserClass
        from pillar.api.projects import routes as proj_routes

        g.current_user = UserClass.construct('', user)

        proj_routes.create_project(overrides={
            'url': 'default-project',
            'is_private': False
        })
Example #5
0
def create_blog(proj_url):
    """Adds a blog to the project."""

    from pillar.api.utils.authentication import force_cli_user
    from pillar.api.utils import node_type_utils
    from pillar.api.node_types.blog import node_type_blog
    from pillar.api.node_types.post import node_type_post
    from pillar.api.utils import remove_private_keys

    force_cli_user()

    db = current_app.db()

    # Add the blog & post node types to the project.
    projects_coll = db['projects']
    proj = projects_coll.find_one({'url': proj_url})
    if not proj:
        log.error('Project url=%s not found', proj_url)
        return 3

    node_type_utils.add_to_project(proj, (node_type_blog, node_type_post),
                                   replace_existing=False)

    proj_id = proj['_id']
    r, _, _, status = current_app.put_internal('projects',
                                               remove_private_keys(proj),
                                               _id=proj_id)
    if status != 200:
        log.error('Error %i storing altered project %s %s', status, proj_id, r)
        return 4
    log.info('Project saved succesfully.')

    # Create a blog node.
    nodes_coll = db['nodes']
    blog = nodes_coll.find_one({'node_type': 'blog', 'project': proj_id})
    if not blog:
        blog = {
            'node_type': node_type_blog['name'],
            'name': 'Blog',
            'description': '',
            'properties': {},
            'project': proj_id,
        }
        r, _, _, status = current_app.post_internal('nodes', blog)
        if status != 201:
            log.error('Error %i storing blog node: %s', status, r)
            return 4
        log.info('Blog node saved succesfully: %s', r)
    else:
        log.info('Blog node already exists: %s', blog)

    return 0
Example #6
0
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)

    # Create the project itself, the rest will be done by the after-insert hook.
    project = {
        'description': '',
        'name': project_name,
        'node_types': [],
        'status': 'published',
        'user': user_id,
        'is_private': True,
        'permissions': {},
        'url': '',
        'summary': '',
        'category': 'assets',  # TODO: allow the user to choose this.
    }
    if overrides is not None:
        project.update(overrides)

    result, _, _, status = current_app.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)

    # 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)

    return project
Example #7
0
def after_inserting_project(project, db_user):
    from pillar.auth import UserClass

    project_id = project['_id']
    user_id = db_user['_id']

    # Create a project-specific admin group (with name matching the project id)
    result, _, _, status = current_app.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.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)

    result, _, _, status = current_app.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
    owner_user = UserClass.construct('', db_user)
    is_admin = authorization.is_admin(owner_user)
    world_permissions = ['GET'] if is_admin else []
    permissions = {
        'world':
        world_permissions,
        'users': [],
        'groups': [
            {
                'group': admin_group_id,
                'methods': DEFAULT_ADMIN_GROUP_PERMISSIONS[:]
            },
        ]
    }

    def with_permissions(node_type):
        copied = copy.deepcopy(node_type)
        copied['permissions'] = permissions
        return copied

    # Assign permissions to the project itself, as well as to the node_types
    project['permissions'] = permissions
    project['node_types'] = [
        with_permissions(node_type_group),
        with_permissions(node_type_asset),
        with_permissions(node_type_comment),
        with_permissions(node_type_texture),
        with_permissions(node_type_group_texture),
    ]

    # Allow admin users to use whatever url they want.
    if not is_admin or not project.get('url'):
        if project.get('category', '') == 'home':
            project['url'] = 'home'
        else:
            project['url'] = "p-{!s}".format(project_id)

    # Initialize storage using the default specified in STORAGE_BACKEND
    default_storage_backend(str(project_id))

    # Commit the changes directly to the MongoDB; a PUT is not allowed yet,
    # as the project doesn't have a valid permission structure.
    projects_collection = current_app.data.driver.db['projects']
    result = projects_collection.update_one(
        {'_id': project_id}, {'$set': remove_private_keys(project)})
    if result.matched_count != 1:
        log.error('Unable to update project %s: %s', project_id,
                  result.raw_result)
        abort_with_error(500)
Example #8
0
def insert_or_fetch_user(wh_payload: dict) -> typing.Optional[dict]:
    """Fetch the user from the DB or create it.

    Only creates it if the webhook payload indicates they could actually use
    Blender Cloud (i.e. demo or subscriber). This prevents us from creating
    Cloud accounts for Blender Network users.

    :returns the user document, or None when not created.
    """

    users_coll = current_app.db('users')
    my_log = log.getChild('insert_or_fetch_user')

    bid_str = str(wh_payload['id'])
    email = wh_payload['email']

    # Find the user by their Blender ID, or any of their email addresses.
    # We use one query to find all matching users. This is done as a
    # consistency check; if more than one user is returned, we know the
    # database is inconsistent with Blender ID and can emit a warning
    # about this.
    query = {'$or': [
        {'auth.provider': 'blender-id', 'auth.user_id': bid_str},
        {'email': {'$in': [wh_payload['old_email'], email]}},
    ]}
    db_users = list(users_coll.find(query))
    user_count = len(db_users)
    if user_count > 1:
        # Now we have to pay the price for finding users in one query; we
        # have to prioritise them and return the one we think is most reliable.
        calc_score = functools.partial(score, wh_payload)
        best_score = max(db_users, key=calc_score)

        my_log.error('%d users found for query %s, picking user %s (%s)',
                     user_count, query, best_score['_id'], best_score['email'])
        return best_score
    if user_count:
        db_user = db_users[0]
        my_log.debug('found user %s', db_user['email'])
        return db_user

    # Pretend to create the user, so that we can inspect the resulting
    # capabilities. This is more future-proof than looking at the list
    # of roles in the webhook payload.
    username = make_unique_username(email)
    user_doc = create_new_user_document(email, bid_str, username,
                                        provider='blender-id',
                                        full_name=wh_payload['full_name'])

    # Figure out the user's eventual roles. These aren't stored in the document yet,
    # because that's handled by the badger service.
    eventual_roles = [subscription.ROLES_BID_TO_PILLAR[r]
                      for r in wh_payload.get('roles', [])
                      if r in subscription.ROLES_BID_TO_PILLAR]
    user_ob = UserClass.construct('', user_doc)
    user_ob.roles = eventual_roles
    user_ob.collect_capabilities()
    create = (user_ob.has_cap('subscriber') or
              user_ob.has_cap('can-renew-subscription') or
              current_app.org_manager.user_is_unknown_member(email))
    if not create:
        my_log.info('Received update for unknown user %r without Cloud access (caps=%s)',
                    wh_payload['old_email'], user_ob.capabilities)
        return None

    # Actually create the user in the database.
    r, _, _, status = current_app.post_internal('users', user_doc)
    if status != 201:
        my_log.error('unable to create user %s: : %r %r', email, status, r)
        raise wz_exceptions.InternalServerError('unable to create user')

    user_doc.update(r)
    my_log.info('created user %r = %s to allow immediate Cloud access', email, user_doc['_id'])
    return user_doc