def _assert_no_credentials_change(previous: user_pb2.User, new: user_pb2.User) -> None: if previous.facebook_id != new.facebook_id: flask.abort(403, "Impossible de modifier l'identifiant Facebook.") if previous.google_id != new.google_id: flask.abort(403, "Impossible de modifier l'identifiant Google.") if previous.profile.email == new.profile.email: return if (new.facebook_id and not previous.profile.email) or new.google_id: # Email address can be changed for Google SSO users and can be set when empty for FB users. if not _EMAIL_REGEX.match(new.profile.email): flask.abort(403, 'Adresse email invalide.') email_taken = bool(flask.current_app.config['USER_DATABASE'].user.find( { 'hashedEmail': auth.hash_user_email(new.profile.email) }, { '_id': 1 }).limit(1).count()) if email_taken: flask.abort( 403, "L'utilisateur existe mais utilise un autre moyen de connexion." ) return flask.abort(403, "Impossible de modifier l'adresse email.")
def _update_returning_user( user_data: user_pb2.User, force_update: bool = False, has_set_email: bool = False) \ -> timestamp_pb2.Timestamp: if user_data.HasField('requested_by_user_at_date'): start_of_day = now.get().replace(hour=0, minute=0, second=0, microsecond=0) if user_data.requested_by_user_at_date.ToDatetime() >= start_of_day: if force_update: _save_low_level(user_data) return user_data.requested_by_user_at_date else: last_connection = timestamp_pb2.Timestamp() last_connection.CopyFrom(user_data.requested_by_user_at_date) else: last_connection = user_data.registered_at if user_data.profile.email: user_data.hashed_email = auth.hash_user_email(user_data.profile.email) if has_set_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.requested_by_user_at_date.FromDatetime(now.get()) # No need to pollute our DB with super precise timestamps. user_data.requested_by_user_at_date.nanos = 0 _save_low_level(user_data) return last_connection
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 delete_user( user_data: user_pb2.User, token: str, *, user_db: Optional[mongo.UsersDatabase] = None) -> Optional[str]: """Delete a user.""" if not user_db: user_db = _get_user_db() filter_user: Optional[dict[str, Any]] if user_data.user_id: try: auth_token.check_token(user_data.user_id, token, role='unsubscribe') except ValueError: try: auth_token.check_token(user_data.user_id, token, role='auth') except ValueError: flask.abort( 403, i18n.flask_translate("Mauvais jeton d'authentification")) filter_user = {'_id': safe_object_id(user_data.user_id)} elif user_data.profile.email: try: auth_token.check_google_token(token, re.compile('@bayesimpact.org')) except i18n.TranslatableException: try: auth_token.check_admin_token(token) except ValueError: flask.abort( 403, i18n.flask_translate( 'Accès refusé, action seulement pour le super-administrateur.' )) filter_user = user_db.user.find_one( {'hashedEmail': auth.hash_user_email(user_data.profile.email)}, {'_id': 1}) else: flask.abort( 400, i18n.flask_translate( 'Impossible de supprimer un utilisateur sans son ID.')) if not filter_user: return None user_proto = get_user_data(str(filter_user['_id']), collection=user_db.user) if not auth.delete_user(user_proto, user_db): flask.abort( 500, i18n.flask_translate( 'Erreur serveur, impossible de supprimer le compte.')) return str(filter_user['_id'])
def create_use_case(request: use_case_pb2.UseCaseCreateRequest, requester_email: str) \ -> use_case_pb2.UseCase: """Create a use case from a user.""" database = flask.current_app.config['EVAL_DATABASE'] user_database = flask.current_app.config['USER_DATABASE'] identifier = request.WhichOneof('identifier') if not identifier: flask.abort(400, "Il manque un identifiant pour créer le cas d'usage.") if request.email: _log_request(request.email, requester_email, database) query = {'hashedEmail': auth.hash_user_email(request.email)} elif request.ticket_id: query = {'supportTickets.ticketId': request.ticket_id} else: query = {'_id': objectid.ObjectId(request.user_id)} # Find user. user_dict = user_database.user.find_one(query) if not user_dict: flask.abort( 404, f'Aucun utilisateur avec l\'identifiant "{getattr(request, identifier)}" ' f"({identifier}) n\'a été trouvé.") # Find next free index in use case pool. last_use_case_in_pool = database.use_case.find( { 'poolName': request.pool_name }, { '_id': 0, 'indexInPool': 1 }, ).sort('indexInPool', pymongo.DESCENDING).limit(1) next_index = next( (u.get('indexInPool', 0) for u in last_use_case_in_pool), -1) + 1 # Convert user to use case. use_case_proto = privacy.user_to_use_case(user_dict, request.pool_name, next_index) if not use_case_proto: flask.abort(500, 'Impossible to read user data.') if not request.pool_name: return use_case_proto # Save use case. use_case = json_format.MessageToDict(use_case_proto) use_case['_id'] = use_case.pop('useCaseId') database.use_case.insert_one(use_case) return use_case_proto
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
if user_data.HasField('requested_by_user_at_date'): start_of_day = now.get().replace(hour=0, minute=0, second=0, microsecond=0) if user_data.requested_by_user_at_date.ToDatetime() >= start_of_day: if force_update: save_low_level(user_data) return user_data.requested_by_user_at_date last_connection = timestamp_pb2.Timestamp() last_connection.CopyFrom(user_data.requested_by_user_at_date) else: last_connection = user_data.registered_at if user_data.profile.email: user_data.hashed_email = auth.hash_user_email(user_data.profile.email) if last_connection.ToDatetime() < datetime.datetime(2019, 10, 25) and \ user_data.features_enabled.strat_two != features_pb2.ACTIVE: for project in user_data.projects: _save_project(project, project, user_data) common_proto.set_date_now(user_data.requested_by_user_at_date) save_low_level(user_data) return last_connection def _is_test_user(user_proto: user_pb2.User) -> bool: return bool(_TEST_USER_REGEXP.search(user_proto.profile.email))
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