def delete_user(user_proto: user_pb2.User, user_database: mongo.UsersDatabase) -> bool: """Close a user's account. We assume the given user_proto is up-to-date, e.g. just being fetched from database. """ try: user_id = objectid.ObjectId(user_proto.user_id) except bson.errors.InvalidId: logging.exception('Tried to delete a user with invalid ID "%s"', user_proto.user_id) return False filter_user = {'_id': user_id} # Remove authentication informations. user_database.user_auth.delete_one(filter_user) try: privacy.redact_proto(user_proto) except TypeError: logging.exception('Cannot delete account %s', str(user_id)) return False user_proto.deleted_at.FromDatetime(now.get()) user_proto.ClearField('user_id') user_database.user.replace_one(filter_user, json_format.MessageToDict(user_proto)) return True
def save_user(user_data: user_pb2.User) -> user_pb2.User: """Save a user in the database.""" unused_, users_database, unused_ = mongo.get_connections_from_env() users_database = users_database.with_prefix('jobflix_') collection = users_database.user if user_data.profile.email: if db_user := collection.find_one( {'hashedEmail': (hashed_email := auth.hash_user_email(user_data.profile.email))}, {'_id': 1, 'projects': 1}): user_data.user_id = str(db_user['_id']) new_projects = list(user_data.projects[:]) user_data.ClearField('projects') user_data.projects.extend( proto.create_from_mongo(p, project_pb2.Project, always_create=True) for p in db_user.get('projects', [])) old_project_ids = {p.project_id for p in user_data.projects} user_data.projects.extend( p for p in new_projects if _make_project_id(p) not in old_project_ids) elif user_data.user_id: collection.update_one({'_id': objectid.ObjectId(user_data.user_id)}, {'$set': { 'profile.email': user_data.profile.email, 'hashedEmail': hashed_email, }})
def _copy_unmodifiable_fields(previous_user_data: user_pb2.User, user_data: user_pb2.User) -> None: """Copy unmodifiable fields. Some fields cannot be changed by the API: we only copy over the fields from the previous state. """ if _is_test_user(user_data): # Test users can do whatever they want. return for field in ('features_enabled', 'last_email_sent_at'): typed_field = typing.cast( Literal['features_enabled', 'last_email_sent_at'], field) if previous_user_data.HasField(typed_field): getattr(user_data, typed_field).CopyFrom( getattr(previous_user_data, typed_field)) else: user_data.ClearField(typed_field)
def save_user(user_data: user_pb2.User, is_new_user: bool, collection: Optional[pymongo.Collection] = None, save_project: _SaveProject = _save_project) -> user_pb2.User: """Save a user, updating all the necessary computed fields while doing so.""" tick.tick('Save user start') if is_new_user: previous_user_data = user_data features.assign_features(user_data.features_enabled, is_new=True) else: tick.tick('Load old user data') previous_user_data = get_user_data(user_data.user_id, collection=collection) if user_data.revision and previous_user_data.revision > user_data.revision: # Do not overwrite newer data that was saved already: just return it. return previous_user_data features.assign_features(previous_user_data.features_enabled, is_new=False) if not previous_user_data.registered_at.seconds: common_proto.set_date_now(user_data.registered_at) # Disable Advisor for new users in tests. if ADVISOR_DISABLED_FOR_TESTING: user_data.features_enabled.advisor = features_pb2.CONTROL user_data.features_enabled.advisor_email = features_pb2.CONTROL elif not _is_test_user(previous_user_data): user_data.registered_at.CopyFrom(previous_user_data.registered_at) user_data.features_enabled.advisor = previous_user_data.features_enabled.advisor user_data.features_enabled.strat_two = previous_user_data.features_enabled.strat_two # TODO(pascal): Clean up those multiple populate_feature_flags floating around. _populate_feature_flags(user_data) for project in user_data.projects: previous_project = next((p for p in previous_user_data.projects if p.project_id == project.project_id), project_pb2.Project()) save_project(project, previous_project, user_data) if user_data.profile.coaching_email_frequency != \ previous_user_data.profile.coaching_email_frequency: # Invalidate the send_coaching_email_after field: it will be recomputed # by the focus email script. user_data.ClearField('send_coaching_email_after') # Update hashed_email field if necessary, to make sure it's consistent with profile.email. This # must be done for all users, since (say) a Google authenticated user may try to connect with # password, so its email hash must be indexed. if user_data.profile.email: user_data.hashed_email = auth.hash_user_email(user_data.profile.email) if not is_new_user: _assert_no_credentials_change(previous_user_data, user_data) _copy_unmodifiable_fields(previous_user_data, user_data) _populate_feature_flags(user_data) user_data.revision += 1 tick.tick('Save user') save_low_level(user_data, is_new_user=is_new_user, collection=collection) tick.tick('Return user proto') return user_data
def save_user(user_data: user_pb2.User, is_new_user: bool) \ -> user_pb2.User: """Save a user, updating all the necessary computed fields while doing so.""" _tick('Save user start') if is_new_user: previous_user_data = user_data else: _tick('Load old user data') previous_user_data = get_user_data(user_data.user_id) if user_data.revision and previous_user_data.revision > user_data.revision: # Do not overwrite newer data that was saved already: just return it. return previous_user_data if not previous_user_data.registered_at.seconds: user_data.registered_at.FromDatetime(now.get()) # No need to pollute our DB with super precise timestamps. user_data.registered_at.nanos = 0 # Disable Advisor for new users in tests. if ADVISOR_DISABLED_FOR_TESTING: user_data.features_enabled.advisor = user_pb2.CONTROL user_data.features_enabled.advisor_email = user_pb2.CONTROL elif not _is_test_user(previous_user_data): user_data.registered_at.CopyFrom(previous_user_data.registered_at) user_data.features_enabled.advisor = previous_user_data.features_enabled.advisor user_data.features_enabled.strat_two = previous_user_data.features_enabled.strat_two _populate_feature_flags(user_data) for project in user_data.projects: previous_project = next((p for p in previous_user_data.projects if p.project_id == project.project_id), project_pb2.Project()) _save_project(project, previous_project, user_data) if user_data.profile.coaching_email_frequency != \ previous_user_data.profile.coaching_email_frequency: # Invalidate the send_coaching_email_after field: it will be recomputed # by the focus email script. user_data.ClearField('send_coaching_email_after') # Update hashed_email field if necessary, to make sure it's consistent with profile.email. This # must be done for all users, since (say) a Google authenticated user may try to connect with # password, so its email hash must be indexed. if user_data.profile.email: user_data.hashed_email = auth.hash_user_email(user_data.profile.email) if not is_new_user: _assert_no_credentials_change(previous_user_data, user_data) _copy_unmodifiable_fields(previous_user_data, user_data) _populate_feature_flags(user_data) if user_data.profile.email and not previous_user_data.profile.email: base_url = parse.urljoin(flask.request.base_url, '/')[:-1] advisor.maybe_send_late_activation_emails( user_data, flask.current_app.config['DATABASE'], base_url) user_data.revision += 1 _tick('Save user') _save_low_level(user_data, is_new_user=is_new_user) _tick('Return user proto') return user_data