Example #1
0
def update_feed_document_update(document, user_id, update_types):
    """Update the feed entry for a document:

    - update `area_ids` if the geometry has changed.
    - update `activities` if figures have changed.
    - update `user_ids` if the document is an outing and the participants
      have changed.

    Only when updating `user_ids`, the "actor" of the feed entry is changed.
    And only then the time is updated and the `change_type` set to `updated`
    to push the entry to the top of the feed.
    """
    if document.redirects_to:
        # TODO delete existing feed entry?
        # see https://github.com/c2corg/v6_api/issues/386
        return
    if document.type in [IMAGE_TYPE, USERPROFILE_TYPE, AREA_TYPE]:
        return

    DBSession.flush()

    # update areas
    if UpdateType.GEOM in update_types:
        update_areas_of_changes(document)

    # updates activities
    if document.type in [ARTICLE_TYPE, OUTING_TYPE, ROUTE_TYPE] and \
            UpdateType.FIGURES in update_types:
        update_activities_of_changes(document)

    # update users_ids/participants (only for outings)
    if document.type != OUTING_TYPE:
        return

    update_participants_of_outing(document.document_id, user_id)
Example #2
0
    def _put(self, clazz, schema):
        id = self.request.validated['id']
        document_in = \
            schema.objectify(self.request.validated['document'])
        self._check_document_id(id, document_in.document_id)

        # get the current version of the document
        document = self._get_document(clazz, id)
        self._check_versions(document, document_in)

        # remember the current version numbers of the document
        old_versions = document.get_versions()

        # update the document with the input document
        document.update(document_in)

        try:
            DBSession.flush()
        except StaleDataError:
            raise HTTPConflict('concurrent modification')

        # when flushing the session, SQLAlchemy automatically updates the
        # version numbers in case attributes have changed. by comparing with
        # the old version numbers, we can check if only figures or only locales
        # have changed.
        (update_type, changed_langs) = \
            self._check_update_type(document, old_versions)
        self._update_version(
            document, self.request.validated['message'], update_type,
            changed_langs)

        return to_json_dict(document, schema)
Example #3
0
    def post(self):
        request = self.request
        user = request.validated['user']

        if user.blocked:
            raise HTTPForbidden('account blocked')

        user.update_validation_nonce(
                Purpose.new_password,
                VALIDATION_EXPIRE_DAYS)

        try:
            DBSession.flush()
        except Exception:
            log.warning('Error persisting user', exc_info=True)
            raise HTTPInternalServerError('Error persisting user')

        email_service = get_email_service(request)
        nonce = user.validation_nonce
        settings = request.registry.settings
        link = settings['mail.request_password_change_url_template'].format(
            '#', nonce)
        email_service.send_request_change_password(user, link)

        return {}
Example #4
0
    def _collection_post(
            self, schema, before_add=None, after_add=None):
        user_id = self.request.authenticated_userid
        document_in = self.request.validated
        document = schema.objectify(document_in)
        document.document_id = None

        if before_add:
            before_add(document, user_id=user_id)

        DBSession.add(document)
        DBSession.flush()
        DocumentRest.create_new_version(document, user_id)

        if document.type != AREA_TYPE:
            update_areas_for_document(document, reset=False)

        if after_add:
            after_add(document, user_id=user_id)

        if document_in.get('associations', None):
            create_associations(document, document_in['associations'], user_id)

        notify_es_syncer(self.request.registry.queue_config)

        return {'document_id': document.document_id}
Example #5
0
    def _collection_post(self, schema, before_add=None, after_add=None):
        user_id = self.request.authenticated_userid
        document_in = self.request.validated
        document = schema.objectify(document_in)
        document.document_id = None

        if before_add:
            before_add(document, user_id=user_id)

        DBSession.add(document)
        DBSession.flush()
        DocumentRest.create_new_version(document, user_id)

        if document.type != AREA_TYPE:
            update_areas_for_document(document, reset=False)

        if after_add:
            after_add(document, user_id=user_id)

        if document_in.get('associations', None):
            create_associations(document, document_in['associations'], user_id)

        notify_es_syncer(self.request.registry.queue_config)

        return {'document_id': document.document_id}
Example #6
0
def update_feed_document_create(document, user_id):
    """Creates a new entry in the feed table when creating a new document.
    """
    if document.redirects_to or \
            document.type in NO_FEED_DOCUMENT_TYPES:
        return

    # make sure all updates are written to the database, so that areas and
    # users can be queried
    DBSession.flush()

    activities = []
    if document.type in [ARTICLE_TYPE, OUTING_TYPE, ROUTE_TYPE]:
        activities = document.activities

    user_ids = [user_id]
    if document.type == OUTING_TYPE:
        participant_ids = _get_participants_of_outing(document.document_id)
        user_ids = list(set(user_ids).union(participant_ids))

    area_ids = []
    if document.type != ARTICLE_TYPE:
        area_ids = _get_area_ids(document)

    change = DocumentChange(
        user_id=user_id,
        change_type='created',
        document_id=document.document_id,
        document_type=document.type,
        activities=activities,
        area_ids=area_ids,
        user_ids=user_ids
    )
    DBSession.add(change)
    DBSession.flush()
Example #7
0
    def post(self):
        request = self.request
        user = request.validated['user']
        user.password = request.validated['password']

        # The user was validated by the nonce so we can log in
        token = log_validated_user_i_know_what_i_do(user, request)

        if token:
            settings = request.registry.settings
            response = token_to_response(user, token, request)
            try:
                client = get_discourse_client(settings)
                r = client.redirect_without_nonce(user)
                response['redirect_internal'] = r
            except:
                # Since only the password is changed, any error with discourse
                # must not prevent login and validation.
                log.error(
                    'Error logging into discourse for %d', user.id,
                    exc_info=True)

            user.clear_validation_nonce()
            try:
                DBSession.flush()
            except:
                log.warning('Error persisting user', exc_info=True)
                raise HTTPInternalServerError('Error persisting user')

            return response
        else:
            request.errors.status = 403
            request.errors.add('body', 'user', 'Login failed')
            return None
Example #8
0
    def collection_post(self):
        settings = self.request.registry.settings

        locale = self.request.validated['locale']

        title = "{}_{}".format(locale.document_id, locale.lang)
        content = '<a href="{}">{}</a>'.format(
                self.request.referer,
                locale.title)
        category = settings['discourse.category']
        # category could be id or name
        try:
            category = int(category)
        except:
            pass

        client = get_discourse_client(settings)
        try:
            response = client.client.create_post(content,
                                                 title=title,
                                                 category=category)
        except:
            raise HTTPInternalServerError('Error with Discourse')

        if "topic_id" in response:
            document_topic = DocumentTopic(topic_id=response['topic_id'])
            locale.document_topic = document_topic
            update_cache_version_direct(locale.document_id)
            DBSession.flush()

        return response
Example #9
0
    def update_version(document, user_id, comment, update_types,
                       changed_langs):
        assert user_id
        assert update_types

        meta_data = HistoryMetaData(comment=comment, user_id=user_id)
        archive = DocumentRest._get_document_archive(document, update_types)
        geometry_archive = \
            DocumentRest._get_geometry_archive(document, update_types)

        langs = DocumentRest._get_langs_to_update(document, update_types,
                                                  changed_langs)
        locale_versions = []
        for lang in langs:
            locale = document.get_locale(lang)
            locale_archive = DocumentRest._get_locale_archive(
                locale, changed_langs)

            version = DocumentVersion(
                document_id=document.document_id,
                lang=locale.lang,
                document_archive=archive,
                document_geometry_archive=geometry_archive,
                document_locales_archive=locale_archive,
                history_metadata=meta_data)
            locale_versions.append(version)

        DBSession.add(archive)
        DBSession.add(meta_data)
        DBSession.add_all(locale_versions)
        DBSession.flush()
Example #10
0
    def post(self):
        request = self.request
        user = request.validated['user']
        user.password = request.validated['password']

        # The user was validated by the nonce so we can log in
        token = log_validated_user_i_know_what_i_do(user, request)

        if token:
            settings = request.registry.settings
            response = token_to_response(user, token, request)
            try:
                client = get_discourse_client(settings)
                r = client.redirect_without_nonce(user)
                response['redirect_internal'] = r
            except:
                # Since only the password is changed, any error with discourse
                # must not prevent login and validation.
                log.error('Error logging into discourse for %d',
                          user.id,
                          exc_info=True)

            user.clear_validation_nonce()
            try:
                DBSession.flush()
            except:
                log.warning('Error persisting user', exc_info=True)
                raise HTTPInternalServerError('Error persisting user')

            return response
        else:
            request.errors.status = 403
            request.errors.add('body', 'user', 'Login failed')
            return None
Example #11
0
    def _update_version(self, document, comment, update_types, changed_langs):
        assert update_types

        meta_data = HistoryMetaData(comment=comment)
        archive = self._get_document_archive(document, update_types)
        geometry_archive = \
            self._get_geometry_archive(document, update_types)

        cultures = \
            self._get_cultures_to_update(document, update_types, changed_langs)
        locale_versions = []
        for culture in cultures:
            locale = document.get_locale(culture)
            locale_archive = self._get_locale_archive(locale, changed_langs)

            version = DocumentVersion(
                document_id=document.document_id,
                culture=locale.culture,
                document_archive=archive,
                document_geometry_archive=geometry_archive,
                document_locales_archive=locale_archive,
                history_metadata=meta_data)
            locale_versions.append(version)

        DBSession.add(archive)
        DBSession.add(meta_data)
        DBSession.add_all(locale_versions)
        DBSession.flush()
Example #12
0
def remove_token(token):
    now = datetime.datetime.utcnow()
    condition = Token.value == token and Token.expire > now
    result = DBSession.execute(Token.__table__.delete().where(condition))
    if result.rowcount == 0:
        log.debug("Failed to remove token %s" % token)
    DBSession.flush()
Example #13
0
    def post(self):
        user = schema_create_user.objectify(self.request.validated)
        user.password = self.request.validated['password']
        user.update_validation_nonce(Purpose.registration,
                                     VALIDATION_EXPIRE_DAYS)

        # directly create the user profile, the document id of the profile
        # is the user id
        lang = user.lang
        user.profile = UserProfile(
            categories=['amateur'],
            locales=[DocumentLocale(lang=lang, title='')])

        DBSession.add(user)
        try:
            DBSession.flush()
        except:
            log.warning('Error persisting user', exc_info=True)
            raise HTTPInternalServerError('Error persisting user')

        # also create a version for the profile
        DocumentRest.create_new_version(user.profile, user.id)

        # The user needs validation
        email_service = get_email_service(self.request)
        nonce = user.validation_nonce
        settings = self.request.registry.settings
        link = settings['mail.validate_register_url_template'] % nonce
        email_service.send_registration_confirmation(user, link)

        return to_json_dict(user, schema_user)
Example #14
0
def remove_token(token):
    now = datetime.datetime.utcnow()
    condition = Token.value == token and Token.expire > now
    result = DBSession.execute(Token.__table__.delete().where(condition))
    if result.rowcount == 0:
        log.debug('Failed to remove token %s' % token)
    DBSession.flush()
Example #15
0
    def _put(self, clazz, schema):
        id = self.request.validated['id']
        document_in = \
            schema.objectify(self.request.validated['document'])
        self._check_document_id(id, document_in.document_id)

        # get the current version of the document
        document = self._get_document(clazz, id)
        self._check_versions(document, document_in)

        # remember the current version numbers of the document
        old_versions = document.get_versions()

        # update the document with the input document
        document.update(document_in)

        try:
            DBSession.flush()
        except StaleDataError:
            raise HTTPConflict('concurrent modification')

        # when flushing the session, SQLAlchemy automatically updates the
        # version numbers in case attributes have changed. by comparing with
        # the old version numbers, we can check if only figures or only locales
        # have changed.
        (update_type, changed_langs) = \
            self._check_update_type(document, old_versions)
        self._update_version(document, self.request.validated['message'],
                             update_type, changed_langs)

        return to_json_dict(document, schema)
Example #16
0
    def _update_version(self, document, user_id, comment, update_types,
                        changed_langs):
        assert user_id
        assert update_types

        meta_data = HistoryMetaData(comment=comment, user_id=user_id)
        archive = self._get_document_archive(document, update_types)
        geometry_archive = \
            self._get_geometry_archive(document, update_types)

        cultures = \
            self._get_cultures_to_update(document, update_types, changed_langs)
        locale_versions = []
        for culture in cultures:
            locale = document.get_locale(culture)
            locale_archive = self._get_locale_archive(locale, changed_langs)

            version = DocumentVersion(
                document_id=document.document_id,
                culture=locale.culture,
                document_archive=archive,
                document_geometry_archive=geometry_archive,
                document_locales_archive=locale_archive,
                history_metadata=meta_data
            )
            locale_versions.append(version)

        DBSession.add(archive)
        DBSession.add(meta_data)
        DBSession.add_all(locale_versions)
        DBSession.flush()
Example #17
0
    def post(self):
        user = schema_create_user.objectify(self.request.validated)
        user.password = self.request.validated['password']
        user.update_validation_nonce(
                Purpose.registration,
                VALIDATION_EXPIRE_DAYS)

        # directly create the user profile, the document id of the profile
        # is the user id
        lang = user.lang
        user.profile = UserProfile(
            categories=['amateur'],
            locales=[DocumentLocale(lang=lang, title='')]
        )

        DBSession.add(user)
        try:
            DBSession.flush()
        except:
            log.warning('Error persisting user', exc_info=True)
            raise HTTPInternalServerError('Error persisting user')

        # also create a version for the profile
        DocumentRest.create_new_version(user.profile, user.id)

        # The user needs validation
        email_service = get_email_service(self.request)
        nonce = user.validation_nonce
        settings = self.request.registry.settings
        link = settings['mail.validate_register_url_template'] % nonce
        email_service.send_registration_confirmation(user, link)

        return to_json_dict(user, schema_user)
Example #18
0
def update_feed_document_update(document, user_id, update_types):
    """Update the feed entry for a document:

    - update `area_ids` if the geometry has changed.
    - update `activities` if figures have changed.
    - update `user_ids` if the document is an outing and the participants
      have changed.

    Only when updating `user_ids`, the "actor" of the feed entry is changed.
    And only then the time is updated and the `change_type` set to `updated`
    to push the entry to the top of the feed.
    """
    if document.redirects_to:
        # TODO delete existing feed entry?
        # see https://github.com/c2corg/v6_api/issues/386
        return
    if document.type in [IMAGE_TYPE, USERPROFILE_TYPE, AREA_TYPE]:
        return

    DBSession.flush()

    # update areas
    if UpdateType.GEOM in update_types:
        update_areas_of_changes(document)

    # updates activities
    if document.type in [ARTICLE_TYPE, OUTING_TYPE, ROUTE_TYPE] and \
            UpdateType.FIGURES in update_types:
        update_activities_of_changes(document)

    # update users_ids/participants (only for outings)
    if document.type != OUTING_TYPE:
        return

    update_participants_of_outing(document.document_id, user_id)
Example #19
0
def update_feed_document_create(document, user_id):
    """Creates a new entry in the feed table when creating a new document.
    """
    if document.redirects_to or \
            document.type in NO_FEED_DOCUMENT_TYPES:
        return

    # make sure all updates are written to the database, so that areas and
    # users can be queried
    DBSession.flush()

    activities = []
    if document.type in [ARTICLE_TYPE, OUTING_TYPE, ROUTE_TYPE]:
        activities = document.activities

    user_ids = [user_id]
    if document.type == OUTING_TYPE:
        participant_ids = _get_participants_of_outing(document.document_id)
        user_ids = list(set(user_ids).union(participant_ids))

    area_ids = []
    if document.type != ARTICLE_TYPE:
        area_ids = _get_area_ids(document)

    change = DocumentChange(
        user_id=user_id,
        change_type='created',
        document_id=document.document_id,
        document_type=document.type,
        activities=activities,
        area_ids=area_ids,
        user_ids=user_ids
    )
    DBSession.add(change)
    DBSession.flush()
Example #20
0
    def create_new_version(document, user_id, written_at=None):
        assert user_id
        archive = document.to_archive()
        archive_locales = document.get_archive_locales()
        archive_geometry = document.get_archive_geometry()

        meta_data = HistoryMetaData(comment='creation',
                                    user_id=user_id,
                                    written_at=written_at)
        versions = []
        for locale in archive_locales:
            version = DocumentVersion(
                document_id=document.document_id,
                lang=locale.lang,
                document_archive=archive,
                document_locales_archive=locale,
                document_geometry_archive=archive_geometry,
                history_metadata=meta_data)
            versions.append(version)

        DBSession.add(archive)
        DBSession.add_all(archive_locales)
        DBSession.add(meta_data)
        DBSession.add_all(versions)
        DBSession.flush()
Example #21
0
    def _put(
            self, clazz, schema, clazz_locale=None, before_update=None,
            after_update=None):
        user_id = self.request.authenticated_userid
        id = self.request.validated['id']
        document_in = \
            schema.objectify(self.request.validated['document'])
        self._check_document_id(id, document_in.document_id)

        # get the current version of the document
        document = self._get_document(clazz, id, clazz_locale=clazz_locale)

        if document.redirects_to:
            raise HTTPBadRequest('can not update merged document')
        if document.protected and not self.request.has_permission('moderator'):
            raise HTTPForbidden('No permission to change a protected document')

        self._check_versions(document, document_in)

        # remember the current version numbers of the document
        old_versions = document.get_versions()

        # update the document with the input document
        document.update(document_in)

        if before_update:
            before_update(document, document_in, user_id=user_id)

        try:
            DBSession.flush()
        except StaleDataError:
            raise HTTPConflict('concurrent modification')

        # when flushing the session, SQLAlchemy automatically updates the
        # version numbers in case attributes have changed. by comparing with
        # the old version numbers, we can check if only figures or only locales
        # have changed.
        (update_types, changed_langs) = document.get_update_type(old_versions)

        if update_types:
            # A new version needs to be created and persisted
            self._update_version(
                document, user_id, self.request.validated['message'],
                update_types,  changed_langs)

            if document.type != AREA_TYPE and UpdateType.GEOM in update_types:
                update_areas_for_document(document, reset=True)

            if after_update:
                after_update(document, update_types, user_id=user_id)

            # And the search updated
            notify_es_syncer(self.request.registry.queue_config)

        associations = self.request.validated.get('associations', None)
        if associations:
            synchronize_associations(document, associations, user_id)

        return {}
Example #22
0
def add_or_retrieve_token(value, expire, userid):
    token = DBSession.query(Token).filter(Token.value == value and User.id == userid).first()
    if not token:
        token = Token(value=value, expire=expire, userid=userid)
        DBSession.add(token)
        DBSession.flush()

    return token
Example #23
0
def add_or_retrieve_token(value, expire, userid):
    token = DBSession.query(Token). \
        filter(Token.value == value, User.id == userid).first()
    if not token:
        token = Token(value=value, expire=expire, userid=userid)
        DBSession.add(token)
        DBSession.flush()

    return token
Example #24
0
    def post(self):
        user = schema_create_user.objectify(self.request.validated)
        user.password = self.request.validated['password']

        DBSession.add(user)
        try:
            DBSession.flush()
        except:
            # TODO: log the error for debugging
            raise HTTPInternalServerError('Error persisting user')

        return to_json_dict(user, schema_user)
Example #25
0
    def _collection_post(self, clazz, schema):
        document = schema.objectify(self.request.validated)
        document.document_id = None

        DBSession.add(document)
        DBSession.flush()

        user_id = self.request.authenticated_userid
        self._create_new_version(document, user_id)

        sync_search_index(document)

        return to_json_dict(document, schema)
Example #26
0
    def _collection_post(self, clazz, schema):
        document = schema.objectify(self.request.validated)
        document.document_id = None

        # TODO additional validation: at least one culture, only one instance
        # for each culture, geometry

        DBSession.add(document)
        DBSession.flush()

        self._create_new_version(document)

        return to_json_dict(document, schema)
Example #27
0
    def _collection_post(self, clazz, schema):
        document = schema.objectify(self.request.validated)
        document.document_id = None

        # TODO additional validation: at least one culture, only one instance
        # for each culture, geometry

        DBSession.add(document)
        DBSession.flush()

        self._create_new_version(document)

        return to_json_dict(document, schema)
Example #28
0
def update_feed_images_upload(images, images_in, user_id):
    """When uploading a set of images, create a feed entry for the document
     the images are linked to.
    """
    if not images or not images_in:
        return
    assert len(images) == len(images_in)

    # get the document that the images were uploaded to
    document_id, document_type = get_linked_document(images_in)
    if not document_id or not document_type:
        return

    image1_id, image2_id, image3_id, more_images = get_images(
        images, images_in, document_id, document_type)

    if not image1_id:
        return

    # load the feed entry for the images
    change = get_existing_change(document_id)
    if not change:
        log.warn('no feed change for document {}'.format(document_id))
        return

    if change.user_id == user_id:
        # if the same user, only update time and change_type.
        # this avoids that multiple entries are shown in the feed for the same
        # document.
        change.change_type = 'updated'
        change.time = func.now()
    else:
        # if different user: first try to get an existing feed entry of the
        # user for the document
        change_by_user = get_existing_change_for_user(document_id, user_id)
        if change_by_user:
            change = change_by_user
            change.change_type = 'added_photos'
            change.time = func.now()
        else:
            change = change.copy()
            change.change_type = 'added_photos'
            change.user_id = user_id
            change.user_ids = list(set(change.user_ids).union([user_id]))

    _update_images(change, image1_id, image2_id, image3_id, more_images)

    DBSession.add(change)
    DBSession.flush()
Example #29
0
    def _put(self, clazz, schema):
        user_id = self.request.authenticated_userid
        id = self.request.validated['id']
        document_in = \
            schema.objectify(self.request.validated['document'])
        self._check_document_id(id, document_in.document_id)

        # get the current version of the document
        document = self._get_document(clazz, id)
        self._check_versions(document, document_in)

        # remember the current version numbers of the document
        old_versions = document.get_versions()

        # find out whether the update of the geometry should be skipped.
        skip_geometry_update = document_in.geometry is None

        # update the document with the input document
        document.update(document_in)

        try:
            DBSession.flush()
        except StaleDataError:
            raise HTTPConflict('concurrent modification')

        # when flushing the session, SQLAlchemy automatically updates the
        # version numbers in case attributes have changed. by comparing with
        # the old version numbers, we can check if only figures or only locales
        # have changed.
        (update_type, changed_langs) = document.get_update_type(old_versions)

        if update_type:
            # A new version needs to be created and persisted
            self._update_version(
                document, user_id, self.request.validated['message'],
                update_type,  changed_langs)

            # And the search updated
            sync_search_index(document)

        json_dict = to_json_dict(document, schema)

        if skip_geometry_update:
            # Optimization: the geometry is not sent back if the client
            # requested to skip the geometry update. Geometries may be very
            # huge; this optimization should speed the data transfer.
            json_dict['geometry'] = None

        return json_dict
Example #30
0
    def collection_post(self):
        settings = self.request.registry.settings

        locale = self.request.validated['locale']
        document = self.request.validated['document']
        document_type = association_keys_for_types[document.type]

        document_path = "/{}/{}/{}".format(document_type, locale.document_id,
                                           locale.lang)

        content = '<a href="https://www.camptocamp.org{}">{}</a>'.format(
            document_path, locale.title or document_path)

        category = settings['discourse.category']
        # category could be id or name
        try:
            category = int(category)
        except Exception:
            pass

        client = get_discourse_client(settings)
        try:
            title = "{}_{}".format(locale.document_id, locale.lang)
            response = client.client.create_post(content,
                                                 title=title,
                                                 category=category)
        except Exception as e:
            log.error('Error with Discourse: {}'.format(str(e)), exc_info=True)
            raise HTTPInternalServerError('Error with Discourse')

        if "topic_id" in response:
            topic_id = response['topic_id']

            document_topic = DocumentTopic(topic_id=topic_id)
            locale.document_topic = document_topic
            update_cache_version_direct(locale.document_id)
            DBSession.flush()

            if locale.type == document_types.OUTING_TYPE:
                try:
                    self.invite_participants(client, locale, topic_id)
                except Exception:
                    log.error(
                        'Inviting participants of outing {} failed'.format(
                            locale.document_id),
                        exc_info=True)

        return response
Example #31
0
    def _create_document(self,
                         document_in,
                         schema,
                         before_add=None,
                         after_add=None,
                         allow_anonymous=False):
        if allow_anonymous and document_in.get('anonymous') and \
           self.request.registry.anonymous_user_id:
            user_id = self.request.registry.anonymous_user_id
        else:
            user_id = self.request.authenticated_userid

        document = schema.objectify(document_in)
        document.document_id = None

        if before_add:
            before_add(document, user_id)

        DBSession.add(document)
        DBSession.flush()
        DocumentRest.create_new_version(document, user_id)

        if document.type != AREA_TYPE:
            update_areas_for_document(document, reset=False)

        if document.type != MAP_TYPE:
            update_maps_for_document(document, reset=False)

        if after_add:
            after_add(document, user_id=user_id)

        if document_in.get('associations', None):
            check_association = association_permission_checker(
                self.request, skip_outing_check=document.type == OUTING_TYPE)

            added_associations = create_associations(
                document,
                document_in['associations'],
                user_id,
                check_association=check_association)
            update_cache_version_associations(added_associations, [],
                                              document.document_id)

        update_feed_document_create(document, user_id)

        notify_es_syncer(self.request.registry.queue_config)
        return document
Example #32
0
def update_feed_images_upload(images, images_in, user_id):
    """When uploading a set of images, create a feed entry for the document
     the images are linked to.
    """
    if not images or not images_in:
        return
    assert len(images) == len(images_in)

    # get the document that the images were uploaded to
    document_id, document_type = get_linked_document(images_in)
    if not document_id or not document_type:
        return

    image1_id, image2_id, image3_id, more_images = get_images(
        images, images_in, document_id, document_type)

    if not image1_id:
        return

    # load the feed entry for the images
    change = get_existing_change(document_id)
    if not change:
        log.warn('no feed change for document {}'.format(document_id))
        return

    if change.user_id == user_id:
        # if the same user, only update time and change_type.
        # this avoids that multiple entries are shown in the feed for the same
        # document.
        change.change_type = 'updated'
        change.time = func.now()
    else:
        # if different user: copy the feed entry
        change = change.copy()
        change.change_type = 'added_photos'
        change.user_id = user_id
        change.user_ids = list(set(change.user_ids).union([user_id]))

    change.image1_id = image1_id
    change.image2_id = image2_id
    change.image3_id = image3_id
    change.more_images = more_images

    DBSession.add(change)
    DBSession.flush()
Example #33
0
    def post(self):
        request = self.request
        user = request.validated["user"]
        user.update_validation_nonce(Purpose.new_password, VALIDATION_EXPIRE_DAYS)

        try:
            DBSession.flush()
        except:
            log.warning("Error persisting user", exc_info=True)
            raise HTTPInternalServerError("Error persisting user")

        email_service = get_email_service(request)
        nonce = user.validation_nonce
        settings = request.registry.settings
        link = settings["mail.request_password_change_url_template"].format("#", nonce)
        email_service.send_request_change_password(user, link)

        return {}
Example #34
0
    def post(self):
        request = self.request
        user = request.validated['user']
        user.clear_validation_nonce()
        user.email = user.email_to_validate
        user.email_to_validate = None

        # Synchronize the new email (and other parameters)
        try:
            client = get_discourse_client(request.registry.settings)
            client.sync_sso(user)
        except:
            log.error('Error syncing email with discourse', exc_info=True)
            raise HTTPInternalServerError('Error with Discourse')

        try:
            DBSession.flush()
        except:
            log.warning('Error persisting user', exc_info=True)
            raise HTTPInternalServerError('Error persisting user')
Example #35
0
    def post(self):
        request = self.request
        user = request.validated['user']
        user.clear_validation_nonce()
        user.email = user.email_to_validate
        user.email_to_validate = None

        # Synchronize the new email (and other parameters)
        try:
            client = get_discourse_client(request.registry.settings)
            client.sync_sso(user)
        except:
            log.error('Error syncing email with discourse', exc_info=True)
            raise HTTPInternalServerError('Error with Discourse')

        try:
            DBSession.flush()
        except:
            log.warning('Error persisting user', exc_info=True)
            raise HTTPInternalServerError('Error persisting user')
Example #36
0
    def post(self):
        request = self.request
        user = request.validated['user']
        user.clear_validation_nonce()
        user.email_validated = True

        # the user profile can be indexed once the account is confirmed
        notify_es_syncer(self.request.registry.queue_config)

        # Synchronizing to Discourse is unnecessary as it will be done
        # during the redirect_without_nonce call below.

        # The user was validated by the nonce so we can log in
        token = log_validated_user_i_know_what_i_do(user, request)

        if token:
            response = token_to_response(user, token, request)
            settings = request.registry.settings
            try:
                client = get_discourse_client(settings)
                r = client.redirect_without_nonce(user)
                response['redirect_internal'] = r
            except:
                # Any error with discourse must prevent login and validation
                log.error(
                    'Error logging into discourse for %d', user.id,
                    exc_info=True)
                raise HTTPInternalServerError('Error with Discourse')

            try:
                DBSession.flush()
            except:
                log.warning('Error persisting user', exc_info=True)
                raise HTTPInternalServerError('Error persisting user')

            return response
        else:
            request.errors.status = 403
            request.errors.add('body', 'user', 'Login failed')
            return None
Example #37
0
    def post(self):
        request = self.request
        user = request.validated['user']
        user.clear_validation_nonce()
        user.email_validated = True

        # the user profile can be indexed once the account is confirmed
        notify_es_syncer(self.request.registry.queue_config)

        # Synchronizing to Discourse is unnecessary as it will be done
        # during the redirect_without_nonce call below.

        # The user was validated by the nonce so we can log in
        token = log_validated_user_i_know_what_i_do(user, request)

        if token:
            response = token_to_response(user, token, request)
            settings = request.registry.settings
            try:
                client = get_discourse_client(settings)
                r = client.redirect_without_nonce(user)
                response['redirect_internal'] = r
            except:
                # Any error with discourse must prevent login and validation
                log.error('Error logging into discourse for %d',
                          user.id,
                          exc_info=True)
                raise HTTPInternalServerError('Error with Discourse')

            try:
                DBSession.flush()
            except:
                log.warning('Error persisting user', exc_info=True)
                raise HTTPInternalServerError('Error persisting user')

            return response
        else:
            request.errors.status = 403
            request.errors.add('body', 'user', 'Login failed')
            return None
Example #38
0
    def _create_new_version(self, document):
        archive = document.to_archive()
        archive_locales = document.get_archive_locales()
        archive_geometry = document.get_archive_geometry()

        meta_data = HistoryMetaData(comment='creation')
        versions = []
        for locale in archive_locales:
            version = DocumentVersion(
                document_id=document.document_id,
                culture=locale.culture,
                document_archive=archive,
                document_locales_archive=locale,
                document_geometry_archive=archive_geometry,
                history_metadata=meta_data)
            versions.append(version)

        DBSession.add(archive)
        DBSession.add_all(archive_locales)
        DBSession.add(meta_data)
        DBSession.add_all(versions)
        DBSession.flush()
Example #39
0
def update_participants_of_outing(outing_id, user_id):
    existing_change = get_existing_change(outing_id)

    if not existing_change:
        log.warn('no feed change for document {}'.format(outing_id))
        return

    participant_ids = _get_participants_of_outing(outing_id)
    if set(existing_change.user_ids) == set(participant_ids):
        # participants have not changed, stop
        return
    existing_change.user_ids = participant_ids

    if existing_change.user_id != user_id:
        # a different user is doing this change, only set a different user id
        # if the user is one of the participants (to ignore moderator edits)
        if user_id in participant_ids:
            existing_change.user_id = user_id

    existing_change.change_type = 'updated'
    existing_change.time = func.now()

    DBSession.flush()

    # now also update the participants of other feed entries of the outing:
    # set `user_ids` to the union of the participant ids and the `user_id` of
    # the entry
    participants_and_editor = text(
        'ARRAY(SELECT DISTINCT UNNEST(array_cat('
        '   ARRAY[guidebook.feed_document_changes.user_id], :participants)) '
        'ORDER BY 1)')
    DBSession.execute(
        DocumentChange.__table__.update().
        where(DocumentChange.document_id == outing_id).
        where(DocumentChange.change_id != existing_change.change_id).
        values(user_ids=participants_and_editor),
        {'participants': participant_ids}
    )
Example #40
0
def update_participants_of_outing(outing_id, user_id):
    existing_change = get_existing_change(outing_id)

    if not existing_change:
        log.warn('no feed change for document {}'.format(outing_id))
        return

    participant_ids = _get_participants_of_outing(outing_id)
    if set(existing_change.user_ids) == set(participant_ids):
        # participants have not changed, stop
        return
    existing_change.user_ids = participant_ids

    if existing_change.user_id != user_id:
        # a different user is doing this change, only set a different user id
        # if the user is one of the participants (to ignore moderator edits)
        if user_id in participant_ids:
            existing_change.user_id = user_id

    existing_change.change_type = 'updated'
    existing_change.time = func.now()

    DBSession.flush()

    # now also update the participants of other feed entries of the outing:
    # set `user_ids` to the union of the participant ids and the `user_id` of
    # the entry
    participants_and_editor = text(
        'ARRAY(SELECT DISTINCT UNNEST(array_cat('
        '   ARRAY[guidebook.feed_document_changes.user_id], :participants)) '
        'ORDER BY 1)')
    DBSession.execute(
        DocumentChange.__table__.update().
        where(DocumentChange.document_id == outing_id).
        where(DocumentChange.change_id != existing_change.change_id).
        values(user_ids=participants_and_editor),
        {'participants': participant_ids}
    )
Example #41
0
    def _create_new_version(self, document):
        archive = document.to_archive()
        archive_locales = document.get_archive_locales()
        archive_geometry = document.get_archive_geometry()

        meta_data = HistoryMetaData(comment='creation')
        versions = []
        for locale in archive_locales:
            version = DocumentVersion(
                document_id=document.document_id,
                culture=locale.culture,
                document_archive=archive,
                document_locales_archive=locale,
                document_geometry_archive=archive_geometry,
                history_metadata=meta_data
            )
            versions.append(version)

        DBSession.add(archive)
        DBSession.add_all(archive_locales)
        DBSession.add(meta_data)
        DBSession.add_all(versions)
        DBSession.flush()
Example #42
0
    def _create_document(
            self, document_in, schema, before_add=None, after_add=None):
        user_id = self.request.authenticated_userid

        document = schema.objectify(document_in)
        document.document_id = None

        if before_add:
            before_add(document, user_id)

        DBSession.add(document)
        DBSession.flush()
        DocumentRest.create_new_version(document, user_id)

        if document.type != AREA_TYPE:
            update_areas_for_document(document, reset=False)

        if document.type != MAP_TYPE:
            update_maps_for_document(document, reset=False)

        if after_add:
            after_add(document, user_id=user_id)

        if document_in.get('associations', None):
            check_association = association_permission_checker(
                self.request, skip_outing_check=document.type == OUTING_TYPE)

            added_associations = create_associations(
                document, document_in['associations'], user_id,
                check_association=check_association)
            update_cache_version_associations(
                added_associations, [], document.document_id)

        update_feed_document_create(document, user_id)

        notify_es_syncer(self.request.registry.queue_config)
        return document
Example #43
0
    def create_new_version(document, user_id):
        assert user_id
        archive = document.to_archive()
        archive_locales = document.get_archive_locales()
        archive_geometry = document.get_archive_geometry()

        meta_data = HistoryMetaData(comment='creation', user_id=user_id)
        versions = []
        for locale in archive_locales:
            version = DocumentVersion(
                document_id=document.document_id,
                lang=locale.lang,
                document_archive=archive,
                document_locales_archive=locale,
                document_geometry_archive=archive_geometry,
                history_metadata=meta_data
            )
            versions.append(version)

        DBSession.add(archive)
        DBSession.add_all(archive_locales)
        DBSession.add(meta_data)
        DBSession.add_all(versions)
        DBSession.flush()
Example #44
0
    def post(self):
        """
        Synchronize user details and return authentication url.
        Important: Email addresses need to be validated by external site.
        """
        request = self.request
        sso_key = request.validated['sso_key']
        sso_external_id = request.validated['sso_external_id']
        user = request.validated['sso_user']

        if user is None:
            # create new user
            user = User(
                username=request.validated['username'],
                name=request.validated['name'],
                forum_username=request.validated['forum_username'],
                email=request.validated['email'],
                email_validated=True,  # MUST be validated by external site
                lang=request.validated['lang'],
                password=generate_token()  # random password
            )
            # directly create the user profile, the document id of the profile
            # is the user id
            lang = user.lang
            user.profile = UserProfile(
                categories=['amateur'],
                locales=[DocumentLocale(lang=lang, title='')],
            )
            DBSession.add(user)
            DBSession.flush()

        if sso_external_id is None:
            sso_external_id = SsoExternalId(
                domain=sso_key.domain,
                external_id=request.validated['external_id'],
                user=user,
            )
            DBSession.add(sso_external_id)

        sso_external_id.token = generate_token()
        sso_external_id.expire = sso_expire_from_now()

        client = get_discourse_client(request.registry.settings)
        discourse_userid = call_discourse(get_discourse_userid, client,
                                          user.id)
        if discourse_userid is None:
            call_discourse(client.sync_sso, user)
            discourse_userid = client.get_userid(user.id)  # From cache

        # Groups are added to discourse, not removed
        group_ids = []
        discourse_groups = None
        groups = request.validated['groups'] or ''
        for group_name in groups.split(','):
            if group_name == '':
                continue
            group_id = None
            if discourse_groups is None:
                discourse_groups = call_discourse(client.client.groups)

            group_id = None
            for discourse_group in discourse_groups:
                if discourse_group['name'] == group_name:
                    group_id = discourse_group['id']

            if group_id is None:
                # If group is not found, we ignore it as we want to return
                # a valid token for user authentication
                pass
            else:
                group_ids.append(group_id)

        for group_id in group_ids:
            call_discourse(client.client.add_user_to_group, group_id,
                           discourse_userid)

        return {
            'url':
            '{}/sso-login?no_redirect&{}'.format(
                request.registry.settings['ui.url'],
                urlencode({'token': sso_external_id.token}))
        }
Example #45
0
    def update_document(document,
                        document_in,
                        request,
                        before_update=None,
                        after_update=None,
                        manage_versions=None):
        user_id = request.authenticated_userid

        # remember the current version numbers of the document
        old_versions = document.get_versions()

        if before_update:
            before_update(document, document_in)

        # update the document with the input document
        document.update(document_in)

        if manage_versions:
            manage_versions(document, old_versions)

        try:
            DBSession.flush()
        except StaleDataError:
            raise HTTPConflict('concurrent modification')

        # when flushing the session, SQLAlchemy automatically updates the
        # version numbers in case attributes have changed. by comparing with
        # the old version numbers, we can check if only figures or only locales
        # have changed.
        (update_types, changed_langs) = document.get_update_type(old_versions)

        if update_types:
            # A new version needs to be created and persisted
            DocumentRest.update_version(document, user_id,
                                        request.validated['message'],
                                        update_types, changed_langs)

            if document.type != AREA_TYPE and UpdateType.GEOM in update_types:
                update_areas_for_document(document, reset=True)

            if document.type != MAP_TYPE and UpdateType.GEOM in update_types:
                update_maps_for_document(document, reset=True)

            if after_update:
                after_update(document, update_types, user_id=user_id)

            update_cache_version(document)

        associations = request.validated.get('associations', None)
        if associations:
            check_association_add = \
                association_permission_checker(request)
            check_association_remove = \
                association_permission_removal_checker(request)

            added_associations, removed_associations = \
                synchronize_associations(
                    document, associations, user_id,
                    check_association_add=check_association_add,
                    check_association_remove=check_association_remove)

        if update_types or associations:
            # update search index
            notify_es_syncer(request.registry.queue_config)
            update_feed_document_update(document, user_id, update_types)
        if associations and (removed_associations or added_associations):
            update_cache_version_associations(added_associations,
                                              removed_associations)

        return update_types
Example #46
0
    def post(self):
        user = self.get_user()
        request = self.request
        validated = request.validated

        result = {}

        # Before all, check whether the user knows the current password
        current_password = validated['currentpassword']
        if not user.validate_password(current_password):
            request.errors.add('body', 'currentpassword', 'Invalid password')
            return

        # update password if a new password is provided
        if 'newpassword' in validated:
            user.password = validated['newpassword']

        # start email validation procedure if a new email is provided
        email_link = None
        if 'email' in validated and validated['email'] != user.email:
            user.email_to_validate = validated['email']
            user.update_validation_nonce(
                    Purpose.change_email,
                    VALIDATION_EXPIRE_DAYS)
            email_service = get_email_service(self.request)
            nonce = user.validation_nonce
            settings = request.registry.settings
            link = settings['mail.validate_change_email_url_template'] % nonce
            email_link = link
            result['email'] = validated['email']
            result['sent_email'] = True

        update_search_index = False
        if 'name' in validated:
            user.name = validated['name']
            result['name'] = user.name
            update_search_index = True

        if 'forum_username' in validated:
            user.forum_username = validated['forum_username']
            result['forum_username'] = user.forum_username
            update_search_index = True

        # Synchronize everything except the new email (still stored
        # in the email_to_validate attribute while validation is pending).
        if email_link:
            try:
                client = get_discourse_client(request.registry.settings)
                client.sync_sso(user)
            except:
                log.error('Error syncing with discourse', exc_info=True)
                raise HTTPInternalServerError('Error with Discourse')

        try:
            DBSession.flush()
        except:
            log.warning('Error persisting user', exc_info=True)
            raise HTTPInternalServerError('Error persisting user')

        if email_link:
            email_service.send_change_email_confirmation(user, link)

        if update_search_index:
            # when user name changes, the search index has to be updated
            notify_es_syncer(self.request.registry.queue_config)

        return result
Example #47
0
    def post(self):
        user = self.get_user()
        request = self.request
        validated = request.validated

        result = {}

        # Before all, check whether the user knows the current password
        current_password = validated['currentpassword']
        if not user.validate_password(current_password):
            request.errors.add('body', 'currentpassword', 'Invalid password')
            return

        # update password if a new password is provided
        if 'newpassword' in validated:
            user.password = validated['newpassword']

        # start email validation procedure if a new email is provided
        email_link = None
        if 'email' in validated and validated['email'] != user.email:
            user.email_to_validate = validated['email']
            user.update_validation_nonce(Purpose.change_email,
                                         VALIDATION_EXPIRE_DAYS)
            email_service = get_email_service(self.request)
            nonce = user.validation_nonce
            settings = request.registry.settings
            link = settings['mail.validate_change_email_url_template'] % nonce
            email_link = link
            result['email'] = validated['email']
            result['sent_email'] = True

        update_search_index = False
        if 'name' in validated:
            user.name = validated['name']
            result['name'] = user.name
            update_search_index = True

        if 'forum_username' in validated:
            user.forum_username = validated['forum_username']
            result['forum_username'] = user.forum_username
            update_search_index = True

        # Synchronize everything except the new email (still stored
        # in the email_to_validate attribute while validation is pending).
        if email_link:
            try:
                client = get_discourse_client(request.registry.settings)
                client.sync_sso(user)
            except:
                log.error('Error syncing with discourse', exc_info=True)
                raise HTTPInternalServerError('Error with Discourse')

        try:
            DBSession.flush()
        except:
            log.warning('Error persisting user', exc_info=True)
            raise HTTPInternalServerError('Error persisting user')

        if email_link:
            email_service.send_change_email_confirmation(user, link)

        if update_search_index:
            # when user name changes, the search index has to be updated
            notify_es_syncer(self.request.registry.queue_config)

        return result
Example #48
0
    def _put(
            self, clazz, schema, clazz_locale=None, before_update=None,
            after_update=None):
        user_id = self.request.authenticated_userid
        id = self.request.validated['id']
        document_in = \
            schema.objectify(self.request.validated['document'])
        self._check_document_id(id, document_in.document_id)

        # get the current version of the document
        document = self._get_document(clazz, id, clazz_locale=clazz_locale)

        if document.redirects_to:
            raise HTTPBadRequest('can not update merged document')
        if document.protected and not self.request.has_permission('moderator'):
            raise HTTPForbidden('No permission to change a protected document')

        self._check_versions(document, document_in)

        # remember the current version numbers of the document
        old_versions = document.get_versions()

        # update the document with the input document
        document.update(document_in)

        if before_update:
            before_update(document, document_in, user_id=user_id)

        try:
            DBSession.flush()
        except StaleDataError:
            raise HTTPConflict('concurrent modification')

        # when flushing the session, SQLAlchemy automatically updates the
        # version numbers in case attributes have changed. by comparing with
        # the old version numbers, we can check if only figures or only locales
        # have changed.
        (update_types, changed_langs) = document.get_update_type(old_versions)

        if update_types:
            # A new version needs to be created and persisted
            DocumentRest.update_version(
                document, user_id, self.request.validated['message'],
                update_types,  changed_langs)

            if document.type != AREA_TYPE and UpdateType.GEOM in update_types:
                update_areas_for_document(document, reset=True)

            if document.type != MAP_TYPE and UpdateType.GEOM in update_types:
                update_maps_for_document(document, reset=True)

            if after_update:
                after_update(document, update_types, user_id=user_id)

            update_cache_version(document)

        associations = self.request.validated.get('associations', None)
        if associations:
            check_association_add = \
                association_permission_checker(self.request)
            check_association_remove = \
                association_permission_removal_checker(self.request)

            added_associations, removed_associations = \
                synchronize_associations(
                    document, associations, user_id,
                    check_association_add=check_association_add,
                    check_association_remove=check_association_remove)

        if update_types or associations:
            # update search index
            notify_es_syncer(self.request.registry.queue_config)
            update_feed_document_update(document, user_id, update_types)
        if associations and (removed_associations or added_associations):
            update_cache_version_associations(
                added_associations, removed_associations)

        return {}