예제 #1
0
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.")
예제 #2
0
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
예제 #3
0
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,
            }})
예제 #4
0
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'])
예제 #5
0
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
예제 #6
0
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
예제 #7
0
    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))
예제 #8
0
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