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)
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)
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 {}
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}
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}
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()
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
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
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()
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
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()
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()
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)
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()
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)
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()
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)
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()
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 {}
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
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
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)
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)
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)
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()
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
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
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
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()
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 {}
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')
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
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
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()
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} )
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()
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
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()
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})) }
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
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
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
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 {}