Ejemplo n.º 1
0
    def recursive_update(self, kind, id, params, preview=False):
        """Brute-force changes to an entity and all its children.

        Intentionally EXCLUDES pd entities in this recursion, because there
        could be thousands of entities to move and this could not be done
        without a timeout.

        Creates logs of its activity in case anything needs to be undone. Logs
        are JSON serialization of the set of all entities before changes and
        the set of entities after changes. This way, if some data is erased by
        this function, it can be found again.

        Returns a list of changed entities.

        Lana. Lana. LAAANAAAA. Danger zone.
        """
        if self.user.user_type != 'god':
            raise PermissionDenied()

        # Get the requested entity's children, limiting ourselves to
        # non-deleted ones. Keeping deleted entities around is really only for
        # emergency data recovery.
        entities = self._get_children(kind,
                                      id, [('deleted =', False)],
                                      exclude_kinds=['pd'])

        # keep a record of these entities before they were changed
        before_snapshot = [e.to_dict() for e in entities]

        #   Make all the requested property changes to all the retrieved
        # entities, if those properties exist. It's important to have this
        # flexibility because a single conceptual change (e.g. changing cohort
        # associations of all children) requires various kinds of property
        # updates (e.g. to assc_cohort_list of Activity and cohort of Pd).
        #   Also build a unique set of only the changed entities to make the
        # db.put() as efficient as possible.
        to_put = set()
        for e in entities:
            for k, v in params.items():
                if hasattr(e, k) and getattr(e, k) != v:
                    to_put.add(e)
                    setattr(e, k, v)
        to_put = list(to_put)

        after_snapshot = [e.to_dict() for e in to_put]

        if not preview:
            db.put(list(to_put))

            # save the log
            body = json.dumps({
                'entities before recursive update':
                before_snapshot,
                'entities after recursive update':
                after_snapshot,
            })
            log_entry = LogEntry.create(log_name='recursive_udpate', body=body)
            log_entry.put()

        return to_put
Ejemplo n.º 2
0
 def delete_everything(self):
     if self.user.user_type == 'god' and util.is_development():
         util.delete_everything()
     else:
         raise PermissionDenied("Only gods working on a development server "
                                "can delete everything.")
     return True
Ejemplo n.º 3
0
    def import_links(self, program, session_ordinal, filename):
        """Read in a cloud storage file full of Qualtrics unique links.

        See https://docs.google.com/document/d/1xrTaGf8-f0wyXg5ZnIH1O6uzSv-ro_ei1MpZOlwCXjA/edit
        """
        if self.user.user_type != 'god':
            raise PermissionDenied()

        # Our convention is to read link csv files out of a bucket named by
        # the app id. But for silly google reasons, the app id is prefixed by
        # 's~' internally (something to do with text search indexing). Take off
        # that bit before using the app id.
        app_id = app_identity.get_application_id()
        if app_id[:2] == 's~':
            app_id = app_id[2:]

        # Set GCS path dependent on program and session.
        path = '/{}_unique_qualtrics_links/{}-{}/{}'.format(
            app_id, program.abbreviation, session_ordinal, filename)

        retry_params = gcs.RetryParams()
        links = []

        # Try the gcs transaction
        try:
            f = gcs.open(path, mode='r', retry_params=retry_params)
        except gcs.NotFoundError:
            return ('GCS File not found. Did you upload a new file to the '
                    'bucket?')

        # Try the csv read
        try:
            reader = csv.reader(f)
            for row in reader:
                if row[7] == 'Link':
                    continue
                link = row[7]
                l = QualtricsLink.create(key_name=link,
                                         link=link,
                                         program=program.id,
                                         session_ordinal=session_ordinal)
                links.append(l)
        except Exception as e:
            logging.error(
                'Something went wrong with the CSV import! {}'.format(e))
            logging.error('CSV has been deleted, try uploading again.')
            # Throwing out links
            links = []

        finally:
            f.close()

        gcs.delete(path)

        db.put(links)
        return len(links)
Ejemplo n.º 4
0
 def impersonate(self, target):
     """Set a special user id in the session so get_current_user() returns
     that user. Makes the website look like the impersonate user would see
     it, while the original user remains logged in. Raises
     PermissionDenied."""
     normal_user = self.get_current_user(method='normal')
     if normal_user.can_impersonate(target):
         # set the impersonated user
         self.session['impersonated_user'] = target.id
     else:
         raise PermissionDenied(
             "Not allowed to impersonate {}".format(target.id))
Ejemplo n.º 5
0
    def update(self, kind, id, kwargs):
        entity = core.Model.get_from_path(kind, id)
        if not self.user.has_permission('put', entity):
            raise PermissionDenied()
        # if creating a user, can this user create this TYPE of user
        # this is necessary to check if user can promote target user
        # to the proposed level; that does not get checked in user.can_put()
        if kind == 'user' and 'user_type' in kwargs:
            if not self.user.can_put_user_type(kwargs['user_type']):
                raise PermissionDenied(
                    "{} cannot create users of type {}.".format(
                        self.user.user_type, kwargs['user_type']))

        # some updates require additional validity checks
        if kind in config.kinds_requiring_put_validation:
            kwargs = entity.validate_put(kwargs)

        # run the actual update
        for k, v in kwargs.items():
            setattr(entity, k, v)
        entity.put()
        return entity
Ejemplo n.º 6
0
    def associate(self, action, from_entity, to_entity, put=True):
        logging.info(
            'Api.associate(action={}, from_entity={}, to_entity={}, put={})'.
            format(action, from_entity.id, to_entity.id, put))

        # create the requested association
        from_kind = core.get_kind(from_entity)
        to_kind = core.get_kind(to_entity)
        if not self.user.can_associate(action, from_entity, to_entity):
            raise PermissionDenied("association failure!")
        if action == 'set_owner':
            property_name = 'owned_' + to_kind + '_list'
        elif action == 'associate':
            property_name = 'assc_' + to_kind + '_list'
        relationship_list = getattr(from_entity, property_name)
        if to_entity.id not in relationship_list:
            relationship_list.append(to_entity.id)
        setattr(from_entity, property_name, relationship_list)

        # recurse through cascading relationships

        start_cascade = (action in ['associate', 'set_owner']
                         and from_kind == 'user'
                         and to_kind in config.user_association_cascade)
        if start_cascade:
            for k in config.user_association_cascade[to_kind]:
                # figure out the target entity
                attr = 'assc_{}_list'.format(k)
                # target_id = getattr(to_entity, attr)[0]
                target_list = getattr(to_entity, attr)
                if len(target_list) > 0:
                    target_id = target_list[0]
                    target_entity = core.Model.get_from_path(k, target_id)
                    # only associate if the user isn't ALREADY associated
                    if target_id not in getattr(from_entity, attr):
                        from_entity = self.associate('associate',
                                                     from_entity,
                                                     target_entity,
                                                     put=False)
                # else: we can't cascade because this entity's associations
                # aren't complete. This happens when they are first created,
                # and we don't have to worry about it.

        if not put:
            # avoid multiple db.puts when creating entities
            return from_entity
        else:
            from_entity.put()

        return from_entity
Ejemplo n.º 7
0
    def unassociate(self, action, from_entity, to_entity, put=True):
        logging.info(
            'Api.unassociate(action={}, from_entity={}, to_entity={}, put={})'.
            format(action, from_entity.id, to_entity.id, put))

        # create the requested association
        from_kind = core.get_kind(from_entity)
        to_kind = core.get_kind(to_entity)
        if not self.user.can_associate(action, from_entity, to_entity):
            raise PermissionDenied()
        logging.info("action {}".format(action))
        if action == 'disown':
            property_name = 'owned_' + to_kind + '_list'
        elif action == 'unassociate':
            property_name = 'assc_' + to_kind + '_list'
        relationship_list = getattr(from_entity, property_name)
        if to_entity.id in relationship_list:
            relationship_list.remove(to_entity.id)
        setattr(from_entity, property_name, relationship_list)

        # recurse through cascading relationships
        start_cascade = (action == 'unassociate' and from_kind == 'user'
                         and to_kind in config.user_disassociation_cascade)
        if start_cascade:
            for k in config.user_disassociation_cascade[to_kind]:
                # figure out the target entity
                attr = 'assc_{}_list'.format(k)
                target_id = getattr(to_entity, attr)[0]
                target_entity = core.Model.get_from_path(k, target_id)
                # make sure the user is ALREADY associated with the target
                if target_id in getattr(from_entity, attr):
                    # then actually unassociate
                    from_entity = self.unassociate('unassociate',
                                                   from_entity,
                                                   target_entity,
                                                   put=False)

        if not put:
            # avoid multiple db.puts when creating entities
            return from_entity
        else:
            from_entity.put()

        return from_entity
Ejemplo n.º 8
0
    def delete(self, kind, id):
        logging.info('Api.delete(kind={}, id={})'.format(kind, id))

        entity = core.Model.get_from_path(kind, id)
        if not self.user.has_permission('delete', entity):
            raise PermissionDenied()
        deleted_list = self._get_children(kind, id, [('deleted =', False)])
        cache = {}
        for e in deleted_list:
            # IdModel entities have relationships and need to be disassociated
            # when they are deleted. NamedModel entities (e.g. ShortLink)
            # don't and we can skip this step.
            if isinstance(e, IdModel):
                cache = self._disassociate(e.id, cache=cache)
            e.deleted = True
        # save changes to deleted entities
        db.put(deleted_list)
        # save changes to users which were disassociated from deleted entities
        db.put([e for id, e in cache.items()])
        return True
Ejemplo n.º 9
0
    def create(self, kind, kwargs):
        logging.info('Api.create(kind={}, kwargs={})'.format(kind, kwargs))
        logging.info("Api.create is in transction: {}".format(
            db.is_in_transaction()))

        # check permissions

        # can this user create this type of object?
        if not self.user.can_create(kind):
            raise PermissionDenied("User type {} cannot create {}".format(
                self.user.user_type, kind))
        # if creating a user, can this user create this TYPE of user
        if kind == 'user':
            if not self.user.can_put_user_type(kwargs['user_type']):
                raise PermissionDenied(
                    "{} cannot create users of type {}.".format(
                        self.user.user_type, kwargs['user_type']))

        # create the object

        klass = core.kind_to_class(kind)
        # some updates require additional validity checks
        if kind in config.custom_create:
            # These put and associate themselves; the user is sent in so custom
            # code can check permissions.
            entity = klass.create(self.user, **kwargs)
            return entity
        else:
            # non-custom creates require more work
            entity = klass.create(**kwargs)

        if kind in config.kinds_requiring_put_validation:
            entity.validate_put(kwargs)

        # create initial relationships with the creating user

        action = config.creator_relationships.get(kind, None)
        if action is not None:
            if self.user.user_type == 'public':
                raise Exception(
                    "We should never be associating with the public user.")
            # associate, but don't put the created entity yet, there's more
            # work to do
            self.user = self.associate(action, self.user, entity, put=False)
            self.user.put()  # do put the changes to the creator

        # create required relationships between the created entity and existing
        # non-user entities

        # different types of users have different required relationships
        k = kind if kind != 'user' else entity.user_type
        for kind_to_associate in config.required_associations.get(k, []):
            target_klass = core.kind_to_class(kind_to_associate)
            # the id of the entity to associate must have been passed in
            target = target_klass.get_by_id(kwargs[kind_to_associate])
            entity = self.associate('associate', entity, target, put=False)
        if k in config.optional_associations:
            for kind_to_associate in config.optional_associations[k]:
                # they're optional, so check if the id has been passed in
                if kind_to_associate in kwargs:
                    # if it was, do the association
                    target_klass = core.kind_to_class(kind_to_associate)
                    target = target_klass.get_by_id(kwargs[kind_to_associate])
                    entity = self.associate('associate',
                                            entity,
                                            target,
                                            put=False)

        # At one point we created qualtrics link pds for students here. Now
        # that happens in the program app via the getQualtricsLinks functional
        # node.

        # now we're done, so we can put all the changes to the new entity
        entity.put()

        return entity