Beispiel #1
0
    def test_create_token(self) -> None:
        """Basic usage of create_token."""

        token_1 = token.create_token('*****@*****.**', 'login')
        self.assertTrue(token_1)

        token_2 = token.create_token('*****@*****.**', 'unsubscribe')
        self.assertTrue(token_2)
        self.assertNotEqual(token_1, token_2)

        token_3 = token.create_token('*****@*****.**', 'login')
        self.assertTrue(token_3)
        self.assertNotEqual(token_1, token_3)
Beispiel #2
0
def get_default_coaching_email_vars(user: user_pb2.User,
                                    **unused_kwargs: Any) -> dict[str, Any]:
    """Compute default variables used in all coaching emails."""

    return get_default_vars(user) | {
        'changeEmailSettingsUrl':
        get_bob_link(
            'unsubscribe.html', {
                'auth':
                auth_token.create_token(user.user_id, role='settings'),
                'coachingEmailFrequency':
                email_pb2.EmailFrequency.Name(
                    user.profile.coaching_email_frequency),
                'hl':
                user.profile.locale,
                'user':
                user.user_id,
            }),
        'firstName':
        french.cleanup_firstname(user.profile.name),
        'gender':
        user_profile_pb2.Gender.Name(user.profile.gender),
        # TODO(pascal): Harmonize use of URL suffix (instead of link).
        'statusUpdateUrl':
        get_status_update_link(user),
    }
Beispiel #3
0
    def test_auth_user_id_token_corrupted_data(
            self, mock_warning: mock.MagicMock) -> None:
        """Authenticate using the user ID and a token but data is corrupted."""

        # Create password.
        user_id = self.authenticate_new_user(email='*****@*****.**',
                                             password='******',
                                             first_name='Pascal',
                                             last_name='Corpet')
        timed_token = token.create_token(user_id, is_using_timestamp=True)

        # Screw the data in Mongo.
        self._user_db.user.update_one({}, {'$set': {'revision': {'hack': 1}}})

        response = self.app.post(
            '/api/user/authenticate',
            data=f'{{"userId": "{user_id}", "authToken": "{timed_token}"}}',
            content_type='application/json')
        self.assertEqual(500, response.status_code)
        self.assertIn('Les données utilisateur sont corrompues',
                      response.get_data(as_text=True))
        mock_warning.assert_called_once()
        self.assertIn(
            'Failed to parse revision',
            mock_warning.call_args[0][0] % mock_warning.call_args[0][1:])
Beispiel #4
0
    def test_auth_user_id_token_outdated(self,
                                         mock_time: mock.MagicMock) -> None:
        """Authenticate using the user ID and a very old token."""

        now = time.time()
        mock_time.time.return_value = now

        # Create password.
        user_id = self.authenticate_new_user(email='*****@*****.**',
                                             password='******',
                                             first_name='Pascal',
                                             last_name='Corpet')

        timed_token = token.create_token(user_id, is_using_timestamp=True)

        # 10 days later…
        mock_time.time.return_value = now + 86400 * 10

        response = self.app.post(
            '/api/user/authenticate',
            data=f'{{"userId": "{user_id}", "authToken": "{timed_token}"}}',
            content_type='application/json')
        self.assertEqual(498, response.status_code)
        self.assertIn("Token d'authentification périmé",
                      response.get_data(as_text=True))
Beispiel #5
0
    def _google_authenticate(
            self, auth_request: auth_pb2.AuthRequest) -> auth_pb2.AuthResponse:
        try:
            id_info = token.decode_google_id_token(
                auth_request.google_token_id)
        except i18n.TranslatableException as error:
            flask.abort(401, error.flask_translate())
        response = auth_pb2.AuthResponse()
        user_dict = self._user_collection.find_one(
            {'googleId': id_info['sub']})
        if proto.parse_from_mongo(user_dict, response.authenticated_user,
                                  'user_id'):
            self._handle_returning_user(response)
        else:
            is_existing_user = self._load_user_from_token_or_email(
                auth_request, response.authenticated_user, id_info['email'])
            response.authenticated_user.profile.picture_url = id_info.get(
                'picture', '')
            response.authenticated_user.google_id = id_info['sub']
            response.is_new_user = not response.authenticated_user.has_account
            response.authenticated_user.has_account = True
            if is_existing_user:
                self._handle_returning_user(response, force_update=True)
            else:
                self.save_new_user(response.authenticated_user,
                                   auth_request.user_data)

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

        return response
Beispiel #6
0
    def test_auth_user_id_token(self, mock_time: mock.MagicMock) -> None:
        """Authenticate using the user ID and a token."""

        now = time.time()
        mock_time.time.return_value = now

        # Create password.
        user_id = self.authenticate_new_user(email='*****@*****.**',
                                             password='******',
                                             first_name='Pascal',
                                             last_name='Corpet')

        timed_token = token.create_token(user_id, is_using_timestamp=True)

        # 2 days later…
        mock_time.time.return_value = now + 86400 * 2

        response = self.app.post(
            '/api/user/authenticate',
            data=f'{{"userId": "{user_id}", "authToken": "{timed_token}"}}',
            content_type='application/json')

        auth_response = self.json_from_response(response)
        self.assertEqual(
            'Pascal',
            auth_response.get('authenticatedUser', {}).get('profile',
                                                           {}).get('name'))
        self.assertTrue(auth_response.get('lastAccessAt'))
Beispiel #7
0
    def setUp(self) -> None:
        """Create a user and get its FFS auth token."""

        super().setUp()
        self.user_id, self.auth_token = self.create_user_with_token()
        self.ffs_auth_token = auth_token.create_token(
            self.user_id, role='first-followup-survey')
Beispiel #8
0
def get_default_vars(user: user_pb2.User,
                     **unused_kwargs: Any) -> dict[str, Any]:
    """Compute default variables used in all emails: firstName, gender and unsubscribeLink."""

    return {
        'areEmailsAnswerRead':
        product.bob.are_email_answers_read,
        'baseUrl':
        product.bob.base_url,
        'firstName':
        french.cleanup_firstname(user.profile.name),
        'gender':
        user_profile_pb2.Gender.Name(user.profile.gender),
        'highlightColor':
        product.bob.get_config('highlightColor', '#faf453'),
        'productLogoUrl':
        product.bob.get_config(
            'productLogoUrl',
            'https://t.bob-emploi.fr/tplimg/6u2u/b/oirn/2ugx1.png'),
        'productName':
        product.bob.name,
        'unsubscribeLink':
        get_bob_link(
            'unsubscribe.html', {
                'auth':
                auth_token.create_token(user.profile.email,
                                        role='unsubscribe'),
                'hl':
                user.profile.locale,
                'user':
                user.user_id,
            }),
    }
Beispiel #9
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
Beispiel #10
0
 def _create_guest_user(
         self, auth_request: auth_pb2.AuthRequest) -> auth_pb2.AuthResponse:
     response = auth_pb2.AuthResponse()
     response.authenticated_user.profile.name = auth_request.first_name
     self.save_new_user(response.authenticated_user, auth_request.user_data)
     response.auth_token = token.create_token(
         response.authenticated_user.user_id, 'auth')
     response.is_new_user = True
     return response
Beispiel #11
0
    def test_delete_url(self) -> None:
        """User can be deleted using a simple GET URL and an 'unsubscribe' auth token."""

        unsub_token = auth_token.create_token(self.user_id, role='unsubscribe')
        response = self.app.get(
            f'/api/upskilling/user/delete/{self.user_id}?token={unsub_token}')
        self.assertLess(response.status_code,
                        400,
                        msg=response.get_data(as_text=True))
        self.assertIn('adresse email sera supprimée',
                      response.get_data(as_text=True))
Beispiel #12
0
    def test_auth_user_id_token_unknown_user(self) -> None:
        """Authenticate using a token but the user ID does not correspond to anything."""

        user_id = str(objectid.ObjectId())
        timed_token = token.create_token(user_id, is_using_timestamp=True)

        response = self.app.post(
            '/api/user/authenticate',
            data=f'{{"userId": "{user_id}", "authToken": "{timed_token}"}}',
            content_type='application/json')
        self.assertEqual(404, response.status_code)
        self.assertIn('Utilisateur inconnu', response.get_data(as_text=True))
Beispiel #13
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
Beispiel #14
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
Beispiel #15
0
def _get_nps_vars(user: user_pb2.User, **unused_kwargs: Any) -> dict[str, str]:
    user_id = user.user_id
    return campaign.get_default_vars(user) | {
        'npsFormUrl':
        campaign.get_bob_link(
            '/api/nps', {
                'user':
                user_id,
                'token':
                auth_token.create_token(user_id, 'nps'),
                'redirect':
                campaign.get_bob_link('/retours', {'hl': user.profile.locale}),
            }),
    }
Beispiel #16
0
def _employment_vars(
    user: user_pb2.User,
    *,
    now: datetime.datetime,
    database: mongo.NoPiiMongoDatabase,
    **unused_kwargs: Any,
) -> dict[str, str]:
    """Computes vars for a given user for the employment survey.

    Returns a dict with all vars required for the template.
    """

    num_months_ago = round((now - user.registered_at.ToDatetime()).days / 30.5)
    if num_months_ago <= 0 and not user.features_enabled.alpha:
        raise campaign.DoNotSend(
            f'User registered only recently ({user.registered_at})')

    scoring_project = scoring.ScoringProject(project_pb2.Project(), user,
                                             database)
    registered_since = scoring_project.get_several_months_text(num_months_ago)

    for status in user.employment_status:
        if status.created_at.ToDatetime() > _ONE_MONTH_AGO:
            raise campaign.DoNotSend(
                'User has already updated their employment status less than one month ago.'
            )
    base_params = {
        'user':
        user.user_id,
        'token':
        parse.quote(
            auth_token.create_token(user.user_id, role='employment-status')),
    }
    return campaign.get_default_vars(user) | {
        'registeredSince':
        registered_since,
        'seekingUrl':
        campaign.get_bob_link(
            '/api/employment-status', base_params | {
                'seeking': user_pb2.SeekingStatus.Name(user_pb2.STILL_SEEKING),
                'redirect': campaign.get_bob_link('/statut/en-recherche'),
            }),
        'stopSeekingUrl':
        campaign.get_bob_link(
            '/api/employment-status', base_params | {
                'seeking': user_pb2.SeekingStatus.Name(user_pb2.STOP_SEEKING),
                'redirect': campaign.get_bob_link('/statut/ne-recherche-plus'),
            }),
    }
Beispiel #17
0
def get_status_update_link(user: user_pb2.User) -> str:
    """Make link with token from user ID for RER status update."""

    user_id = user.user_id
    profile = user.profile
    was_employed = any(
        [p.kind == project_pb2.FIND_ANOTHER_JOB for p in user.projects])
    return get_bob_link(
        'statut/mise-a-jour', {
            'employed': str(was_employed),
            'gender': user_profile_pb2.Gender.Name(profile.gender),
            'hl': profile.locale or 'fr',
            'token': auth_token.create_token(user_id,
                                             role='employment-status'),
            'user': user_id,
        })
Beispiel #18
0
    def test_auth_user_id_token_wrong_format_id(self) -> None:
        """Authenticate using a token but a wrongly formatted user ID."""

        # Create password.
        user_id = self.authenticate_new_user(email='*****@*****.**',
                                             password='******',
                                             first_name='Pascal',
                                             last_name='Corpet')

        timed_token = token.create_token(user_id, is_using_timestamp=True)

        response = self.app.post(
            '/api/user/authenticate',
            data=f'{{"userId": "aaa", "authToken": "{timed_token}"}}',
            content_type='application/json')
        self.assertEqual(400, response.status_code)
        self.assertIn(
            'L&#x27;identifiant utilisateur &quot;aaa&quot; n&#x27;a pas le bon format',
            response.get_data(as_text=True))
Beispiel #19
0
def _get_ffs_vars(
    user: user_pb2.User,
    *,
    now: datetime.datetime,
    **unused_kwargs: Any,
) -> dict[str, str]:
    user_id = user.user_id
    days_since_registered = (now - user.registered_at.ToDatetime()).days

    if user.net_promoter_score_survey_response.score:
        raise campaign.DoNotSend('User already answered the NPS survey')

    is_alpha = user.features_enabled.alpha
    if (days_since_registered < 6
            or days_since_registered > 13) and not is_alpha:
        raise campaign.DoNotSend(
            'User registered too long ago or too recently')

    main_challenge_id = user.projects[
        0].diagnostic.category_id if user.projects else ''
    return campaign.get_default_vars(user) | {
        'buttonBackgroundColor':
        '#58bbfb',
        'buttonTextColor':
        '#ffffff',
        'ffsFormUrl':
        campaign.get_bob_link(
            '/api/first-followup-survey', {
                'user':
                user_id,
                'token':
                auth_token.create_token(user_id, 'first-followup-survey'),
                'redirect':
                campaign.get_bob_link(
                    '/first-followup-survey', {
                        'hl': user.profile.locale,
                        'gender': user_profile_pb2.Gender.Name(
                            user.profile.gender),
                        'mainChallenge': main_challenge_id,
                    }),
            }),
    }
Beispiel #20
0
    def test_get_user(self) -> None:
        """Get user info."""

        user_id = self.create_user_with_token(
            data={
                'profile': {
                    'name': 'Pascale',
                    'gender': 'FEMININE'
                },
                'projects': [{
                    'city': {
                        'name': 'Lyon'
                    },
                    'createdAt': '2017-05-31T19:25:01Z',
                    'targetJob': {
                        'name': 'CTO'
                    }
                }]
            })[0]
        nps_auth_token = auth_token.create_token(user_id, role='nps')

        response = self.app.get(
            f'/api/nps/user/{user_id}',
            headers={'Authorization': 'Bearer ' + nps_auth_token})

        user = self.json_from_response(response)
        self.assertEqual(
            {
                'profile': {
                    'gender': 'FEMININE'
                },
                'projects': [{
                    'city': {
                        'name': 'Lyon'
                    },
                    'targetJob': {
                        'name': 'CTO'
                    },
                }],
            }, user)
Beispiel #21
0
    def _facebook_authenticate(
            self, auth_request: auth_pb2.AuthRequest) -> auth_pb2.AuthResponse:
        user_info_response = requests.get(
            'https://graph.facebook.com/v4.0/me',
            params=dict(
                fields='id,first_name,email',
                access_token=auth_request.facebook_access_token,
            ))
        if user_info_response.status_code < 200 or user_info_response.status_code >= 300:
            flask.abort(
                user_info_response.status_code,
                user_info_response.json().get('error', {}).get('message', ''))
        user_info = typing.cast(dict[str, str], user_info_response.json())

        response = auth_pb2.AuthResponse()
        user_dict = self._user_collection.find_one(
            {'facebookId': user_info['id']})
        if proto.parse_from_mongo(user_dict, response.authenticated_user,
                                  'user_id'):
            self._handle_returning_user(response)
        else:
            is_existing_user = self._load_user_from_token_or_email(
                auth_request, response.authenticated_user,
                user_info.get('email'))
            response.authenticated_user.facebook_id = user_info['id']
            response.is_new_user = not response.authenticated_user.has_account
            response.authenticated_user.has_account = True
            if is_existing_user:
                self._handle_returning_user(response, force_update=True)
            else:
                response.authenticated_user.profile.name = user_info.get(
                    'first_name', '')
                self.save_new_user(response.authenticated_user,
                                   auth_request.user_data)

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

        return response
Beispiel #22
0
def get_default_vars(user: user_pb2.User,
                     **unused_kwargs: Any) -> dict[str, Any]:
    """Default template variables for a Jobflix campaign."""

    return {
        'productName':
        product.bob.get_plugin_config('jobflix', 'productName', _PRODUCT),
        'productUrl':
        parse.urljoin(
            product.bob.base_url,
            product.bob.get_plugin_config('jobflix', 'productUrl',
                                          'https://www.jobflix.app')),
        'senderFirstName':
        _COACH_FIRST_NAME,
        'unsubscribeLink':
        campaign.get_bob_link(
            f'/api/upskilling/user/delete/{user.user_id}', {
                'hl':
                user.profile.locale,
                'token':
                parse.quote(
                    auth_token.create_token(user.user_id, role='unsubscribe')),
            }),
    }
Beispiel #23
0
    def test_check_token_wrong_role(self) -> None:
        """check_token fails if wrong role."""

        login_token = token.create_token('*****@*****.**', 'login')
        with self.assertRaises(ValueError):
            token.check_token('*****@*****.**', login_token, 'unsubscribe')
Beispiel #24
0
def _get_vars(user: user_pb2.User, *, now: datetime.datetime,
              database: mongo.NoPiiMongoDatabase,
              **unused_kwargs: Any) -> dict[str, str]:

    if not user.projects or not user.projects[0].actions:
        raise campaign.DoNotSend('User has no project or no actions yet')

    project = user.projects[0]

    most_recent_date = max(d.ToDatetime()
                           for d in (user.registered_at, project.created_at,
                                     project.action_plan_started_at))
    if (now - most_recent_date).days > 7:
        raise campaign.DoNotSend(
            'User has registered a while ago, too late to send the activation')

    # Set locale.
    user_locale = user.profile.locale.split('@', 1)[0]
    date_format = '%d %B %Y'
    if user_locale == 'fr' or not user_locale:
        locale.setlocale(locale.LC_ALL, 'fr_FR.UTF-8')
    elif user_locale == 'en_UK':
        locale.setlocale(locale.LC_ALL, 'en_GB.UTF-8')
        date_format = '%B %d %Y'
    elif user_locale == 'en':
        locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
        date_format = '%B %d %Y'
    else:
        logging.exception('Sending an email with an unknown locale: %s',
                          user_locale)

    scoring_project = scoring.ScoringProject(project, user, database, now=now)
    auth_token = parse.quote(
        token.create_token(user.user_id, is_using_timestamp=True))
    settings_token = parse.quote(
        token.create_token(user.user_id, role='settings'))
    coaching_email_frequency_name = \
        email_pb2.EmailFrequency.Name(user.profile.coaching_email_frequency)
    # This uses tutoiement by default, because its content adressed from the user to a third party
    # (that we assume the user is familiar enough with), not from Bob to the user.
    virality_template = parse.urlencode({
        'body':
        scoring_project.translate_static_string(
            'Salut,\n\n'
            "Est-ce que tu connais Bob\u00A0? C'est un site qui propose de t'aider dans ta "
            "recherche d'emploi en te proposant un diagnostic et des conseils personnalisés. "
            'Tu verras, ça vaut le coup\u00A0: en 15 minutes, tu en sauras plus sur où tu en es, '
            'et ce que tu peux faire pour avancer plus efficacement. '
            "Et en plus, c'est gratuit\u00A0!\n\n"
            '{invite_url}\n\n'
            'En tous cas, bon courage pour la suite,\n\n'
            '{first_name}', ).format(invite_url=parse.urljoin(
                product.bob.base_url, 'invite#vm2m'),
                                     first_name=user.profile.name),
        'subject':
        scoring_project.translate_static_string("Ça m'a fait penser à toi"),
    })
    change_email_settings_url = parse.urljoin(
        product.bob.base_url,
        'unsubscribe.html?' + parse.urlencode({
            'user': user.user_id,
            'auth': settings_token,
            'coachingEmailFrequency': coaching_email_frequency_name,
            'hl': user.profile.locale,
        }))
    team_members = (
        'Tabitha',
        'Paul',
        'John',
        'Pascal',
        'Sil',
        'Cyrille',
        'Flo',
        'Nicolas',
        'Florian',
        'Lillie',
        'Benjamin',
        'Émilie',
    )

    # Selected and pending actions.
    highlighted_actions = [
        action for action in project.actions
        if action.status == action_pb2.ACTION_CURRENT
    ]
    if len(highlighted_actions) < 2:
        highlighted_actions.extend(
            action for action in project.actions
            if action.status == action_pb2.ACTION_UNREAD)
    else:
        highlighted_actions = sorted(
            highlighted_actions,
            key=lambda action: action.expected_completion_at.ToDatetime())
    actions = [{
        'title':
        action.title,
        'url':
        parse.urljoin(
            product.bob.base_url,
            f'/projet/{project.project_id}/action/{action.action_id}'
            f'?userId={user.user_id}&authToken={auth_token}')
    } for action in highlighted_actions[:2]]

    data: dict[str, Any] = campaign.get_default_vars(user)
    data |= {
        'actions':
        actions,
        'changeEmailSettingsUrl':
        change_email_settings_url,
        'coachingEmailFrequency':
        email_pb2.EmailFrequency.Name(user.profile.coaching_email_frequency)
        if user.profile.coaching_email_frequency and
        user.profile.coaching_email_frequency != email_pb2.EMAIL_NONE else '',
        'date':
        now.strftime(date_format),
        'firstTeamMember':
        team_members[0],
        'isActionPlanCompleted':
        project.HasField('action_plan_started_at'),
        'isCoachingEnabled':
        'True' if user.profile.coaching_email_frequency
        and user.profile.coaching_email_frequency != email_pb2.EMAIL_NONE else
        '',
        'loginUrl':
        parse.urljoin(product.bob.base_url,
                      f'?userId={user.user_id}&authToken={auth_token}'),
        'numActions':
        len(actions),
        'numberUsers':
        '270\u00A0000',
        'numberTeamMembers':
        len(team_members),
        'ofJob':
        scoring_project.populate_template('%ofJobName',
                                          raise_on_missing_var=True)
        if project.target_job.name else '',
        'teamMembers':
        ', '.join(team_members[1:]),
        'viralityTemplate':
        f'mailto:?{virality_template}'
    }
    return data
Beispiel #25
0
    def test_check_token(self) -> None:
        """Basic usage of check_token (round trip with create_token)."""

        login_token = token.create_token('*****@*****.**', 'login')
        token.check_token('*****@*****.**', login_token, 'login')
Beispiel #26
0
    def _linked_in_authenticate(
            self, auth_request: auth_pb2.AuthRequest) -> auth_pb2.AuthResponse:
        token_data = _get_oauth2_access_token(
            'https://www.linkedin.com/oauth/v2/accessToken',
            code=auth_request.linked_in_code,
            client_id=_LINKED_IN_CLIENT_ID or '',
            client_secret=_LINKED_IN_CLIENT_SECRET or '',
            auth_name='LinkedIn Auth',
        )

        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.linkedin.com/v2/me',
            headers={'Authorization': authorization_header})
        if user_info_response.status_code < 200 or user_info_response.status_code >= 400:
            logging.warning('LinkedIn Auth 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()
        # TODO(cyrille): Factorize with other 3rd party auth.
        user_dict = self._user_collection.find_one(
            {'linkedInId': user_info['id']})
        if proto.parse_from_mongo(user_dict, response.authenticated_user,
                                  'user_id'):
            self._handle_returning_user(response)
        else:
            email_response = requests.get(
                'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))',
                headers={'Authorization': authorization_header})
            if email_response.status_code < 200 or email_response.status_code >= 400:
                logging.warning('LinkedIn Auth fails (%d): "%s"',
                                email_response.status_code,
                                email_response.text)
                flask.abort(403, email_response.text)
            email_response_json = typing.cast(_LinkedInEmailResponse,
                                              email_response.json())
            email = email_response_json.get('handle~', {}).get('emailAddress')
            user = response.authenticated_user
            is_existing_user = self._load_user_from_token_or_email(
                auth_request, user, email)
            user.linked_in_id = user_info['id']
            response.is_new_user = not user.has_account
            user.has_account = True
            if is_existing_user:
                self._handle_returning_user(response, force_update=True)
            else:
                # TODO(pascal): Handle the case where one of the name is missing.
                user.profile.name = user_info.get('localizedFirstName', '')
                user.profile.last_name = user_info.get('localizedLastName', '')
                self.save_new_user(user, auth_request.user_data)

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

        return response
Beispiel #27
0
    def _email_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)

        user_dict = self._user_collection.find_one(
            {'hashedEmail': hash_user_email(auth_request.email)})

        if not user_dict:
            return self._email_register(auth_request, response)

        user_object_id = user_dict['_id']
        user_id = str(user_object_id)

        user_auth_dict = self._user_db.user_auth.find_one(
            {'_id': user_object_id})

        if not auth_request.hashed_password:
            # User exists but did not send a password: probably just getting some fresh salt.
            return response
        if not user_auth_dict:
            if not auth_request.auth_token:
                # User is trying to connect with a password, but never created one.
                self.send_auth_token(user_dict)
                _abort_failed_login()
            try:
                token.check_token(user_id,
                                  auth_request.auth_token,
                                  role='auth')
            except ValueError as error:
                logging.info('Invalid token:\n %s', error)
                _abort_on_bad_auth()
            # User that already uses an SSO account is now trying to add a password.
            _parse_user_from_mongo(user_dict, response.authenticated_user)
            self._user_db.user_auth.insert_one({
                '_id':
                user_object_id,
                'hashedPassword':
                auth_request.hashed_password,
            })
            response.auth_token = token.create_token(user_id, 'auth')
            response.authenticated_user.has_account = True
            response.authenticated_user.has_password = True
            response.is_password_updated = True
            self._handle_returning_user(response, force_update=True)
            return response

        if auth_request.user_id:
            # User is a guest using a pre-existing email account:
            # maybe they're using the same password.
            if auth_request.hashed_password != user_auth_dict.get(
                    'hashedPassword', ''):
                return response
            _parse_user_from_mongo(user_dict, response.authenticated_user)
            return response

        if auth_request.auth_token:
            self._reset_password(auth_request, user_id, user_auth_dict,
                                 user_dict)
            _parse_user_from_mongo(user_dict, response.authenticated_user)
            response.auth_token = token.create_token(user_id, 'auth')
            return response

        if not auth_request.hash_salt:
            # User exists but has not sent salt: probably just getting some fresh salt.
            return response

        # Check that salt is valid.
        salt = auth_request.hash_salt
        try:
            if not token.assert_valid_salt(salt, auth_request.email, instant):
                return response
        except ValueError as error:
            logging.info('Salt has not been generated by this server:\n %s',
                         error)
            _abort_on_bad_auth()

        stored_hashed_password = user_auth_dict.get('hashedPassword', '')

        _check_password(stored_hashed_password, salt,
                        auth_request.hashed_password)

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

        # Update the password.
        if auth_request.new_hashed_password:
            self._user_db.user_auth.replace_one(
                {'_id': user_object_id},
                {'hashedPassword': auth_request.new_hashed_password})
            response.is_password_updated = True
            user_dict['_id'] = user_id
            self.send_update_confirmation(user_dict)

        self._handle_returning_user(response)

        return response
Beispiel #28
0
    def setUp(self) -> None:
        """Create a user and get its nps auth token."""

        super().setUp()
        self.user_id, self.auth_token = self.create_user_with_token()
        self.nps_auth_token = auth_token.create_token(self.user_id, role='nps')