예제 #1
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'])
예제 #2
0
 def _decorated_fun(*args: Any, **kwargs: Any) -> Any:
     auth_token = flask.request.args.get('token')
     user_id = flask.request.args.get('user')
     if not user_id or not auth_token:
         flask.abort(422, i18n.flask_translate('Paramètres manquants.'))
     try:
         token.check_token(user_id, auth_token, role=role)
     except ValueError:
         flask.abort(403, i18n.flask_translate('Accès non autorisé.'))
     return func(*args, **dict(kwargs, user_id=user_id))
예제 #3
0
    def authenticate(
            self, auth_request: auth_pb2.AuthRequest) -> auth_pb2.AuthResponse:
        """Authenticate a user."""

        if auth_request.google_token_id:
            return self._google_authenticate(auth_request)
        if auth_request.facebook_access_token:
            return self._facebook_authenticate(auth_request)
        if auth_request.pe_connect_code:
            return self._pe_connect_authenticate(auth_request)
        if auth_request.linked_in_code:
            return self._linked_in_authenticate(auth_request)
        if auth_request.email:
            return self._email_authenticate(auth_request)
        if auth_request.user_id:
            return self._token_authenticate(auth_request)

        # Create a guest user.
        if auth_request.first_name:
            return self._create_guest_user(auth_request)

        logging.warning('No mean of authentication found:\n%s', auth_request)
        flask.abort(
            422,
            i18n.flask_translate(
                "Aucun moyen d'authentification n'a été trouvé."))
예제 #4
0
def _abort_failed_login() -> None:
    flask.abort(
        403,
        i18n.flask_translate(
            "L'email et le mot de passe ne correspondent pas. " +
            "Si vous avez déjà créé un compte mais que vous n'avez pas créé votre mot de passe, "
            + 'nous venons de vous envoyer un email pour vous connecter.'))
예제 #5
0
    def _pe_connect_authenticate(
            self, auth_request: auth_pb2.AuthRequest) -> auth_pb2.AuthResponse:
        token_data = _get_oauth2_access_token(
            'https://authentification-candidat.pole-emploi.fr/connexion/oauth2/access_token?'
            'realm=/individu',
            code=auth_request.pe_connect_code,
            client_id=_EMPLOI_STORE_CLIENT_ID or '',
            client_secret=_EMPLOI_STORE_CLIENT_SECRET or '',
            auth_name='PE Connect',
        )

        if token_data.get('nonce') != auth_request.pe_connect_nonce:
            flask.abort(403, i18n.flask_translate('Mauvais paramètre nonce'))
        bearer = token_data.get('token_type', 'Bearer')
        access_token = token_data.get('access_token', '')
        authorization_header = f'{bearer} {access_token}'

        user_info_response = requests.get(
            'https://api.emploi-store.fr/partenaire/peconnect-individu/v1/userinfo',
            headers={'Authorization': authorization_header})
        if user_info_response.status_code < 200 or user_info_response.status_code >= 400:
            logging.warning('PE Connect fails (%d): "%s"',
                            user_info_response.status_code,
                            user_info_response.text)
            flask.abort(403, user_info_response.text)

        user_info = typing.cast(dict[str, str], user_info_response.json())

        response = auth_pb2.AuthResponse()
        user_dict = self._user_collection.find_one(
            {'peConnectId': user_info['sub']})
        if proto.parse_from_mongo(user_dict, response.authenticated_user,
                                  'user_id'):
            self._handle_returning_user(response)
        else:
            user = response.authenticated_user
            is_existing_user = self._load_user_from_token_or_email(
                auth_request, user, user_info.get('email'))
            user.pe_connect_id = user_info['sub']
            response.is_new_user = force_update = not user.has_account
            user.has_account = True
            if is_existing_user:
                self._handle_returning_user(response,
                                            force_update=force_update)
            else:
                # TODO(pascal): Handle the case where one of the name is missing.
                user.profile.name = french.cleanup_firstname(
                    user_info.get('given_name', ''))
                user.profile.last_name = french.cleanup_firstname(
                    user_info.get('family_name', ''))
                user.profile.gender = _PE_CONNECT_GENDER.get(
                    user_info.get('gender', ''),
                    user_profile_pb2.UNKNOWN_GENDER)
                self.save_new_user(user, auth_request.user_data)

        response.auth_token = token.create_token(
            response.authenticated_user.user_id, 'auth')

        return response
예제 #6
0
 def _decorated_fun(*args: Any, **kwargs: Any) -> Any:
     auth_token = flask.request.headers.get('Authorization',
                                            '').replace('Bearer ', '')
     if not auth_token:
         auth_token = flask.request.args.get('token', '')
         if not auth_token:
             auth_token = flask.request.cookies.get(
                 _AUTH_TOKEN_COOKIE_NAME, '')
             if not auth_token:
                 flask.abort(401,
                             i18n.flask_translate('Token manquant'))
     user_id = get_user_id(*args, **kwargs)
     try:
         token.check_token(user_id, auth_token, role=role)
     except ValueError:
         flask.abort(403, i18n.flask_translate('Unauthorized token'))
     return func(*args, **kwargs)
예제 #7
0
def delete_user(*, user_id: str) -> Tuple[str, int]:
    """Delete a user from their internal ID."""

    user_db = mongo.get_connections_from_env().user_db.with_prefix('jobflix_')
    auth_token = flask.request.headers.get('Authorization', '').replace('Bearer ', '') or \
        flask.request.args.get('token', '')
    user_data = user_pb2.User(user_id=user_id)
    user_data.profile.email = flask.request.args.get('email', '')
    deleted_id = user.delete_user(user_data, auth_token, user_db=user_db)
    if not deleted_id:
        return i18n.flask_translate(
            "Nous n'avons pas trouvé votre email dans notre base de données.\n"
            'Si vous ne vous étiez pas encore désabonné·e, '
            'contactez nous à [email protected] pour vous assurer de votre désinscription.'), 404
    return i18n.flask_translate(
        'Votre requête a été prise en compte.\n'
        'Votre adresse email sera supprimée de la base Jobflix dans les prochaines 24 heures.'), 202
예제 #8
0
def _parse_user_from_mongo(user_dict: dict[str, Any],
                           user: user_pb2.User) -> None:
    if not proto.parse_from_mongo(user_dict, user, 'user_id'):
        flask.abort(
            500,
            i18n.flask_translate(
                'Les données utilisateur sont corrompues dans la base de données.'
            ))
예제 #9
0
def get_more_jobs(
        user_proto: user_pb2.User, *, section_id: str, state: str) -> upskilling_pb2.Section:
    """Return more jobs for a given section."""

    if not user_proto.projects:
        flask.abort(422, i18n.flask_translate("Il n'y a pas de projet à explorer."))

    try:
        generator_id, section_state = state.split(':', 1)
    except ValueError:
        flask.abort(
            422,
            i18n.flask_translate("Le paramètre d'état {state} n'a pas le bon format.")
            .format(state=state))

    project = user_proto.projects[0]
    database = mongo.get_connections_from_env().stats_db
    scoring_project = scoring.ScoringProject(project, user_proto, database)

    try:
        generator = _SECTION_GENERATORS[generator_id]
    except KeyError:
        flask.abort(
            404,
            i18n.flask_translate('Générateur de section inconnu: {generator_id}')
            .format(generator_id=generator_id))

    try:
        section = generator.get_more_jobs(
            scoring_project=scoring_project, section_id=section_id, state=section_state)
    except _InvalidState:
        flask.abort(
            422,
            i18n.flask_translate('Impossible de commencer à {start_from}')
            .format(start_from=section_state))
    best_jobs_in_area = _get_best_jobs_in_area(scoring_project)
    are_all_jobs_hiring = _get_are_all_jobs_hiring()
    best_salaries = {
        job.job_group.rome_id for job in best_jobs_in_area.best_salaries_jobs}
    for job in section.jobs:
        _add_perks_to_job(job, best_salaries, is_hiring=are_all_jobs_hiring)
    return section
예제 #10
0
def get_project_data(user_proto: user_pb2.User,
                     project_id: str) -> project_pb2.Project:
    """Get the data for a project or abort."""

    try:
        return next(project for project in user_proto.projects
                    if project.project_id == project_id)
    except StopIteration:
        flask.abort(
            404,
            i18n.flask_translate('Projet "{project_id}" inconnu.').format(
                project_id=project_id))
예제 #11
0
    def _token_authenticate(
            self, auth_request: auth_pb2.AuthRequest) -> auth_pb2.AuthResponse:
        instant = int(time.time())
        response = auth_pb2.AuthResponse()
        response.hash_salt = token.timestamped_hash(instant,
                                                    auth_request.email)
        self._load_user_with_token(auth_request, response.authenticated_user)
        if response.authenticated_user.HasField('deleted_at'):
            flask.abort(404, i18n.flask_translate('Compte supprimé'))
        response.auth_token = token.create_token(auth_request.user_id, 'auth')
        self._handle_returning_user(response)

        return response
예제 #12
0
    def _email_register(
            self, auth_request: auth_pb2.AuthRequest, response: auth_pb2.AuthResponse) \
            -> auth_pb2.AuthResponse:
        """Registers a new user using an email address."""

        is_existing_user = self._load_user_with_token(
            auth_request,
            response.authenticated_user,
            is_timestamp_required=False)
        force_update = False
        if not is_existing_user and auth_request.hashed_password:
            if not auth_request.first_name:
                flask.abort(
                    422,
                    i18n.flask_translate('Le champ first_name est nécessaire'))

        should_create_account = not is_existing_user and auth_request.hashed_password
        if is_existing_user or should_create_account:
            force_update |= response.authenticated_user.profile.email != auth_request.email
            response.authenticated_user.profile.email = auth_request.email
            response.authenticated_user.hashed_email = hash_user_email(
                auth_request.email)
            response.is_new_user = not response.authenticated_user.has_account
            force_update |= response.is_new_user
            response.authenticated_user.has_account = True
            force_update |= \
                response.authenticated_user.has_password != bool(auth_request.hashed_password)
            response.authenticated_user.has_password = bool(
                auth_request.hashed_password)

        if is_existing_user:
            self._handle_returning_user(response, force_update=force_update)
        elif should_create_account:
            response.authenticated_user.profile.name = auth_request.first_name
            response.authenticated_user.profile.last_name = auth_request.last_name
            self.save_new_user(response.authenticated_user,
                               auth_request.user_data)

        if auth_request.hashed_password:
            object_id = objectid.ObjectId(response.authenticated_user.user_id)
            self._user_db.user_auth.replace_one(
                {'_id': object_id},
                {'hashedPassword': auth_request.hashed_password},
                upsert=True)
            response.is_password_updated = True

        response.is_new_user = not is_existing_user
        # TODO(cyrille): Consider dropping if there's no user_id.
        response.auth_token = token.create_token(
            response.authenticated_user.user_id, 'auth')
        return response
예제 #13
0
    def change_email(self, user_proto: user_pb2.User, auth_request: auth_pb2.AuthRequest) \
            -> user_pb2.User:
        """Change user's email address."""

        new_hashed_email = hash_user_email(auth_request.email)
        if user_proto.hashed_email == new_hashed_email:
            # Trying to set the same email.
            return user_proto

        user_auth_dict = self._user_db.user_auth.find_one(
            {'_id': objectid.ObjectId(user_proto.user_id)})
        if user_auth_dict or auth_request.hashed_password:
            if not user_auth_dict:
                flask.abort(
                    422,
                    i18n.flask_translate(
                        "L'utilisateur n'a pas encore de mot de passe"))
            stored_hashed_password = user_auth_dict.get('hashedPassword', '')
            _check_password(stored_hashed_password, auth_request.hash_salt,
                            auth_request.hashed_password)

        existing = self._user_collection.find_one(
            {'hashedEmail': new_hashed_email}, {'_id': 1})
        if existing:
            flask.abort(
                403,
                i18n.flask_translate(
                    'L\'email "{email}" est déjà utilisé par un autre compte').
                format(email=auth_request.email))

        user_proto.profile.email = auth_request.email
        user_proto.hashed_email = new_hashed_email
        if user_auth_dict:
            self._user_db.user_auth.replace_one(
                {'_id': objectid.ObjectId(user_proto.user_id)},
                {'hashedPassword': auth_request.new_hashed_password})
        self._update_returning_user(user_proto, force_update=True)
        return user_proto
예제 #14
0
    def _load_user_with_token(self,
                              auth_request: auth_pb2.AuthRequest,
                              out_user: user_pb2.User,
                              is_timestamp_required: bool = True) -> bool:
        if not auth_request.user_id:
            return False
        try:
            user_id = objectid.ObjectId(auth_request.user_id)
        except bson.errors.InvalidId:
            flask.abort(
                400,
                i18n.flask_translate(
                    'L\'identifiant utilisateur "{user_id}" n\'a pas le bon format.',
                ).format(user_id=auth_request.user_id))
        try:
            if not token.assert_valid_salt(
                    auth_request.auth_token, str(user_id), int(time.time()),
                    validity_seconds=datetime.timedelta(days=5).total_seconds(),
                    role='auth') \
                    and is_timestamp_required:
                raise ExpiredTokenException(
                    i18n.flask_translate("Token d'authentification périmé"))
        except ValueError as error:
            flask.abort(
                403,
                i18n.flask_translate(
                    "Le sel n'a pas été généré par ce serveur\xa0: {error}.").
                format(error=error))

        user_dict = self._user_collection.find_one({'_id': user_id})

        if not user_dict:
            flask.abort(404, i18n.flask_translate('Utilisateur inconnu.'))

        _parse_user_from_mongo(user_dict, out_user)

        return True
예제 #15
0
def get_sections_for_project(user_proto: user_pb2.User) -> upskilling_pb2.Sections:
    """Return all the sections to browse."""

    if not user_proto.projects:
        flask.abort(422, i18n.flask_translate("Il n'y a pas de projet à explorer."))
    project = user_proto.projects[0]
    database = mongo.get_connections_from_env().stats_db
    scoring_project = scoring.ScoringProject(project, user_proto, database)

    result = upskilling_pb2.Sections()

    good_jobs = jobs.get_all_good_job_group_ids(scoring_project.database)
    best_salaries = {
        job.job_group.rome_id for job in _get_best_jobs_in_area(scoring_project).best_salaries_jobs}
    slots = list(_SECTION_SLOTS.get_collection(database))
    are_all_jobs_hiring = _get_are_all_jobs_hiring()
    for section in slots:
        if section.is_for_alpha_only and not user_proto.features_enabled.alpha:
            continue
        generator_id = section.generator
        try:
            generator = _SECTION_GENERATORS[generator_id]
        except KeyError:
            logging.error('Unknown upskilling section generator "%s"', generator_id)
            continue
        computed_section = generator.get_jobs(
            scoring_project=scoring_project, allowed_job_ids=good_jobs,
            previous_sections={
                section.id
                for section in result.sections
                if section.state.startswith(f'{generator_id}:')
            })
        if not computed_section or len(computed_section.jobs) < 2:
            continue
        result.sections.add(
            id=computed_section.new_id or generator_id,
            state=f'{generator_id}:{computed_section.state or ""}',
            name=scoring_project.populate_template(scoring_project.translate_key_string(
                f'jobflix_sections:{computed_section.new_id or generator_id}',
                hint=computed_section.new_name or generator.name,
                context=_get_bob_deployment(), is_hint_static=True)),
            jobs=[
                _add_perks_to_job(job, best_salaries, is_hiring=are_all_jobs_hiring)
                for job in computed_section.jobs],
        )

    return result
예제 #16
0
    def _get_google_email(authorization: str) -> str:

        try:
            token.check_admin_token(authorization)
            return '*****@*****.**'
        except ValueError:
            pass
        if authorization.startswith('Bearer '):
            authorization = authorization.removeprefix('Bearer ')
        else:
            authorization = flask.request.args.get('token', '')
            if not authorization:
                flask.abort(401, i18n.flask_translate('Token manquant'))
        try:
            return token.check_google_token(authorization, emails_regexp)
        except i18n.TranslatableException as error:
            flask.abort(401, error.flask_translate())
예제 #17
0
def _get_auth_error_message() -> str:
    return i18n.flask_translate(
        "Les informations d'authentification ne sont pas valides.")