예제 #1
0
def activity_object_add(actor_user_id, verb, object_type, object_id,
                        context_object_type, context_object_id):
    """Add a notification object and creates a notification for each user that
    - is not the original author of the post
    - is actively subscribed to the object

    This works using the following pattern:

    ACTOR -> VERB -> OBJECT -> CONTEXT

    :param actor_user_id: id of the user who is changing the object
    :param verb: the action on the object ('commented', 'replied')
    :param object_type: hardcoded name
    :param object_id: object id, to be traced with object_type_id
    """

    subscriptions = notification_get_subscriptions(
        context_object_type, context_object_id, actor_user_id)

    if subscriptions.count() == 0:
        return

    info, status = register_activity(actor_user_id, verb, object_type, object_id,
                                     context_object_type, context_object_id)
    if status != 201:
        # If creation failed for any reason, do not create a any notifcation
        return

    for subscription in subscriptions:
        notification = dict(
            user=subscription['user'],
            activity=info['_id'])
        current_app.post_internal('notifications', notification)
예제 #2
0
파일: cli.py 프로젝트: jonike/dillo
def setup_db(admin_email):
    """Extends Pillar setup_db."""
    from pillar.cli.setup import setup_db as pillar_setup_db
    # Define the dillo_user_main group, which is automatically assigned
    # after every user creation.
    g = {'name': 'dillo_user_main'}
    current_app.post_internal('groups', g)
    # Execute the default user creation
    pillar_setup_db(admin_email)
예제 #3
0
def store_token(user_id, token: str, token_expiry, oauth_subclient_id=False):
    """Stores an authentication token.

    :returns: the token document from MongoDB
    """

    assert isinstance(token,
                      str), 'token must be string type, not %r' % type(token)

    token_data = {
        'user': user_id,
        'token': token,
        'expire_time': token_expiry,
    }
    if oauth_subclient_id:
        token_data['is_subclient_token'] = True

    r, _, _, status = current_app.post_internal('tokens', token_data)

    if status not in {200, 201}:
        log.error('Unable to store authentication token: %s', r)
        raise RuntimeError('Unable to store authentication token.')

    token_data.update(r)
    return token_data
예제 #4
0
    def api_create_job(self, job_name, job_desc, job_type, job_settings,
                       project_id, user_id, manager_id, priority=50,
                       *, start_paused=False):
        """Creates a job, returning a dict with its generated fields."""

        job = {
            'name': job_name,
            'description': job_desc,
            'job_type': job_type,
            'project': project_id,
            'user': user_id,
            'manager': manager_id,
            'status': 'under-construction',
            'priority': int(priority),
            'settings': copy.deepcopy(job_settings),
        }
        if start_paused:
            job['start_paused'] = True

        self._log.info('Creating job %r for user %s and manager %s',
                       job_name, user_id, manager_id)

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

        job.update(r)
        return job
예제 #5
0
    def api_create_task(self, job, commands, name, parents=None, priority=50,
                        status='queued', *, task_type: str) -> bson.ObjectId:
        """Creates a task in MongoDB for the given job, executing commands.

        Returns the ObjectId of the created task.
        """

        task = {
            'job': job['_id'],
            'manager': job['manager'],
            'user': job['user'],
            'name': name,
            'status': status,
            'job_type': job['job_type'],
            'task_type': task_type,
            'commands': [cmd.to_dict() for cmd in commands],
            'job_priority': job['priority'],
            'priority': priority,
            'project': job['project'],
        }
        # Insertion of None parents is not supported
        if parents:
            task['parents'] = parents

        self._log.info('Creating task %s for manager %s, user %s',
                       name, job['manager'], job['user'])

        r, _, _, status = current_app.post_internal('flamenco_tasks', task)
        if status != 201:
            self._log.error('Error %i creating task %s: %s',
                            status, task, r)
            raise wz_exceptions.InternalServerError('Unable to create task')

        return r['_id']
예제 #6
0
    def api_create_task(self, job, commands, name, parents=None, priority=50,
                        status='queued', *, task_type: str) -> bson.ObjectId:
        """Creates a task in MongoDB for the given job, executing commands.

        Returns the ObjectId of the created task.
        """

        task = {
            'job': job['_id'],
            'manager': job['manager'],
            'user': job['user'],
            'name': name,
            'status': status,
            'job_type': job['job_type'],
            'task_type': task_type,
            'commands': [cmd.to_dict() for cmd in commands],
            'job_priority': job['priority'],
            'priority': priority,
            'project': job['project'],
        }
        # Insertion of None parents is not supported
        if parents:
            task['parents'] = parents

        self._log.info('Creating task %s for manager %s, user %s',
                       name, job['manager'], job['user'])

        r, _, _, status = current_app.post_internal('flamenco_tasks', task)
        if status != 201:
            self._log.error('Error %i creating task %s: %s',
                            status, task, r)
            raise wz_exceptions.InternalServerError('Unable to create task')

        return r['_id']
예제 #7
0
파일: __init__.py 프로젝트: wqlxx/flamenco
    def api_create_job(self, job_name, job_desc, job_type, job_settings,
                       project_id, user_id, manager_id, priority=50):
        """Creates a job, returning a dict with its generated fields."""

        job = {
            'name': job_name,
            'description': job_desc,
            'job_type': job_type,
            'project': project_id,
            'user': user_id,
            'manager': manager_id,
            'status': 'under-construction',
            'priority': int(priority),
            'settings': copy.deepcopy(job_settings),
        }

        self._log.info('Creating job %r for user %s and manager %s',
                       job_name, user_id, manager_id)

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

        job.update(r)
        return job
예제 #8
0
def create_file_doc_for_upload(project_id, uploaded_file):
    """Creates a secure filename and a document in MongoDB for the file.

    The (project_id, filename) tuple should be unique. If such a document already
    exists, it is updated with the new file.

    :param uploaded_file: file from request.files['form-key']
    :type uploaded_file: werkzeug.datastructures.FileStorage
    :returns: a tuple (file_id, filename, status), where 'filename' is the internal
            filename used on GCS.
    """

    project_id = ObjectId(project_id)

    # Hash the filename with path info to get the internal name. This should
    # be unique for the project.
    # internal_filename = uploaded_file.filename
    _, ext = os.path.splitext(uploaded_file.filename)
    internal_filename = uuid.uuid4().hex + ext

    # For now, we don't support overwriting files, and create a new one every time.
    # # See if we can find a pre-existing file doc.
    # files = current_app.data.driver.db['files']
    # file_doc = files.find_one({'project': project_id,
    #                            'name': internal_filename})
    file_doc = None

    # TODO: at some point do name-based and content-based content-type sniffing.
    new_props = {
        'filename': uploaded_file.filename,
        'content_type': uploaded_file.mimetype,
        'length': uploaded_file.content_length,
        'project': project_id,
        'status': 'uploading'
    }

    if file_doc is None:
        # Create a file document on MongoDB for this file.
        file_doc = create_file_doc(name=internal_filename, **new_props)
        file_fields, _, _, status = current_app.post_internal(
            'files', file_doc)
    else:
        file_doc.update(new_props)
        file_fields, _, _, status = current_app.put_internal(
            'files', remove_private_keys(file_doc))

    if status not in (200, 201):
        log.error(
            'Unable to create new file document in MongoDB, status=%i: %s',
            status, file_fields)
        raise wz_exceptions.InternalServerError()

    log.debug(
        'Created file document %s for uploaded file %s; internal name %s',
        file_fields['_id'], uploaded_file.filename, internal_filename)

    return file_fields['_id'], internal_filename, status
예제 #9
0
def activity_subscribe(user_id, context_object_type, context_object_id):
    """Subscribe a user to changes for a specific context. We create a subscription
    if none is found.

    :param user_id: id of the user we are going to subscribe
    :param context_object_type: hardcoded index, check the notifications/model.py
    :param context_object_id: object id, to be traced with context_object_type_id
    """
    subscriptions_collection = current_app.data.driver.db['activities-subscriptions']
    lookup = {
        'user': user_id,
        'context_object_type': context_object_type,
        'context_object': context_object_id
    }
    subscription = subscriptions_collection.find_one(lookup)

    # If no subscription exists, we create one
    if not subscription:
        current_app.post_internal('activities-subscriptions', lookup)
예제 #10
0
def register_activity(actor_user_id,
                      verb,
                      object_type,
                      object_id,
                      context_object_type,
                      context_object_id,
                      project_id=None,
                      node_type=None):
    """Registers an activity.

    This works using the following pattern:

    ACTOR -> VERB -> OBJECT -> CONTEXT

    :param actor_user_id: id of the user who is changing the object
    :param verb: the action on the object ('commented', 'replied')
    :param object_type: hardcoded name, see database schema
    :param object_id: object id, to be traced with object_type
    :param context_object_type: the type of the context object, like 'project' or 'node',
        see database schema
    :param context_object_id:
    :param project_id: optional project ID to make the activity easily queryable
        per project.
    :param node_type: optional, node type of the node receiving the activity.

    :returns: tuple (info, status_code), where a successful operation should have
        status_code=201. If it is not 201, a warning is logged.
    """

    activity = {
        'actor_user': actor_user_id,
        'verb': verb,
        'object_type': object_type,
        'object': object_id,
        'context_object_type': context_object_type,
        'context_object': context_object_id
    }
    if project_id:
        activity['project'] = project_id
    if node_type:
        activity['node_type'] = node_type

    info, _, _, status_code = current_app.post_internal('activities', activity)

    if status_code != 201:
        log.error('register_activity: code %i creating activity %s: %s',
                  status_code, activity, info)
    else:
        log.info('register_activity: user %s "%s" on %s %s, context %s %s',
                 actor_user_id, verb, object_type, object_id,
                 context_object_type, context_object_id)
    return info, status_code
예제 #11
0
def create_new_user(email, username, user_id):
    """Creates a new user in our local database.

    @param email: the user's email
    @param username: the username, which is also used as full name.
    @param user_id: the user ID from the Blender ID server.
    @returns: the user ID from our local database.
    """

    user_data = create_new_user_document(email, user_id, username)
    r = current_app.post_internal('users', user_data)
    user_id = r[0]['_id']
    return user_id
예제 #12
0
def create_blender_sync_node(project_id, admin_group_id, user_id):
    """Creates a node for Blender Sync, with explicit write access for the admin group.

    Writes the node to the database.

    :param project_id: ID of the home project
    :type project_id: ObjectId
    :param admin_group_id: ID of the admin group of the project. This group will
        receive write access to the node.
    :type admin_group_id: ObjectId
    :param user_id: ID of the owner of the node.
    :type user_id: ObjectId

    :returns: The created node.
    :rtype: dict
    """

    log.debug('Creating sync node for project %s, user %s', project_id,
              user_id)

    node = {
        'project': ObjectId(project_id),
        'node_type': 'group',
        'name': SYNC_GROUP_NODE_NAME,
        'user': ObjectId(user_id),
        'description': SYNC_GROUP_NODE_DESC,
        'properties': {
            'status': 'published'
        },
        'permissions': {
            'users': [],
            'groups': [{
                'group': ObjectId(admin_group_id),
                'methods': ['GET', 'PUT', 'POST', 'DELETE']
            }],
            'world': [],
        }
    }

    r, _, _, status = current_app.post_internal('nodes', node)
    if status != 201:
        log.warning(
            'Unable to create Blender Sync node for home project %s: %s',
            project_id, r)
        raise wz_exceptions.InternalServerError(
            'Unable to create Blender Sync node')

    node.update(r)
    return node
예제 #13
0
def create_local_user(email, password):
    """For internal user only. Given username and password, create a user."""
    # Hash the password
    hashed_password = hash_password(password, bcrypt.gensalt())
    db_user = create_new_user_document(email, '', email, provider='local',
                                       token=hashed_password)
    # Make username unique
    db_user['username'] = make_unique_username(email)
    # Create the user
    r, _, _, status = current_app.post_internal('users', db_user)
    if status != 201:
        log.error('internal response: %r %r', status, r)
        return abort(500)
    # Return user ID
    return r['_id']
예제 #14
0
def create_service_account(email: str,
                           roles: typing.Iterable,
                           service: dict,
                           *,
                           full_name: str = None):
    """Creates a service account with the given roles + the role 'service'.

    :param email: optional email address associated with the account.
    :param roles: iterable of role names
    :param service: dict of the 'service' key in the user.
    :param full_name: Full name of the service account. If None, will be set to
        something reasonable.

    :return: tuple (user doc, token doc)
    """

    # Create a user with the correct roles.
    roles = sorted(set(roles).union({'service'}))
    user_id = bson.ObjectId()

    log.info('Creating service account %s with roles %s', user_id, roles)
    user = {
        '_id': user_id,
        'username': f'SRV-{user_id}',
        'groups': [],
        'roles': roles,
        'settings': {
            'email_communications': 0
        },
        'auth': [],
        'full_name': full_name or f'SRV-{user_id}',
        'service': service
    }
    if email:
        user['email'] = email
    result, _, _, status = current_app.post_internal('users', user)

    if status != 201:
        raise ServiceAccountCreationError('Error creating user {}: {}'.format(
            user_id, result))
    user.update(result)

    # Create an authentication token that won't expire for a long time.
    token = generate_auth_token(user['_id'])

    return user, token
예제 #15
0
def _ensure_user_main_group() -> ObjectId:
    """Retrieve the dillo_user_main group.

    If the group does not exist, create it.

    Returns the group ObjectId.
    """
    grp_collection = current_app.data.driver.db['groups']
    dillo_user_main_group = grp_collection.find_one(
        {'name': 'dillo_user_main'})
    if not dillo_user_main_group:
        dillo_user_main_group, _, _, status = current_app.post_internal(
            'groups', {'name': 'dillo_user_main'})
        if status != 201:
            log.error('Unable to create dillo_user_main group')
            return abort_with_error(status)
    return dillo_user_main_group['_id']
예제 #16
0
def store_token(
    user_id,
    token: str,
    token_expiry,
    oauth_subclient_id=False,
    *,
    org_roles: typing.Set[str] = frozenset(),
    oauth_scopes: typing.Optional[typing.List[str]] = None,
):
    """Stores an authentication token.

    :returns: the token document from MongoDB
    """

    assert isinstance(token,
                      str), 'token must be string type, not %r' % type(token)

    token_data = {
        'user': user_id,
        'token': token,
        'expire_time': token_expiry,
    }
    if oauth_subclient_id:
        token_data['is_subclient_token'] = True
    if org_roles:
        token_data['org_roles'] = sorted(org_roles)
    if oauth_scopes:
        token_data['oauth_scopes'] = oauth_scopes

    r, _, _, status = current_app.post_internal('tokens', token_data)

    if status not in {200, 201}:
        log.error('Unable to store authentication token: %s', r)
        raise RuntimeError('Unable to store authentication token.')

    token_data.update(r)
    return token_data
예제 #17
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
예제 #18
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)
예제 #19
0
def upsert_user(db_user):
    """Inserts/updates the user in MongoDB.

    Retries a few times when there are uniqueness issues in the username.

    :returns: the user's database ID and the status of the PUT/POST.
        The status is 201 on insert, and 200 on update.
    :type: (ObjectId, int)
    """

    if 'subscriber' in db_user.get('groups', []):
        log.error('Non-ObjectID string found in user.groups: %s', db_user)
        raise wz_exceptions.InternalServerError(
            'Non-ObjectID string found in user.groups: %s' % db_user)

    if not db_user['full_name']:
        # Blender ID doesn't need a full name, but we do.
        db_user['full_name'] = db_user['username']

    r = {}
    for retry in range(5):
        if '_id' in db_user:
            # Update the existing user
            attempted_eve_method = 'PUT'
            db_id = db_user['_id']
            r, _, _, status = current_app.put_internal(
                'users', remove_private_keys(db_user), _id=db_id)
            if status == 422:
                log.error(
                    'Status %i trying to PUT user %s with values %s, should not happen! %s',
                    status, db_id, remove_private_keys(db_user), r)
        else:
            # Create a new user, retry for non-unique usernames.
            attempted_eve_method = 'POST'
            r, _, _, status = current_app.post_internal('users', db_user)

            if status not in {200, 201}:
                log.error('Status %i trying to create user with values %s: %s',
                          status, db_user, r)
                raise wz_exceptions.InternalServerError()

            db_id = r['_id']
            db_user.update(r)  # update with database/eve-generated fields.

        if status == 422:
            # Probably non-unique username, so retry a few times with different usernames.
            log.info('Error creating new user: %s', r)
            username_issue = r.get('_issues', {}).get('username', '')
            if 'not unique' in username_issue:
                # Retry
                db_user['username'] = make_unique_username(db_user['email'])
                continue

        # Saving was successful, or at least didn't break on a non-unique username.
        break
    else:
        log.error('Unable to create new user %s: %s', db_user, r)
        raise wz_exceptions.InternalServerError()

    if status not in (200, 201):
        log.error('internal response from %s to Eve: %r %r',
                  attempted_eve_method, status, r)
        raise wz_exceptions.InternalServerError()

    return db_id, status