Ejemplo n.º 1
0
    def _facebook_authenticate(self, signed_request):
        try:
            [encoded_signature, payload] = signed_request.split('.')
            data = json.loads(_base64_url_decode(payload).decode('utf-8'))
            actual_signature = _base64_url_decode(encoded_signature)
        except ValueError as error:
            flask.abort(422, error)
        for required_field in ('algorithm', 'user_id'):
            if not data.get(required_field):
                flask.abort(
                    422,
                    'Le champs %s est requis : %s' % (required_field, data))
        if data['algorithm'].lower() != 'hmac-sha256':
            flask.abort(
                422,
                'Algorithme d\'encryption inconnu "%s"' % data['algorithm'])

        expected_signature = hmac.new(_FACEBOOK_SECRET,
                                      payload.encode('utf-8'),
                                      hashlib.sha256).digest()
        if expected_signature != actual_signature:
            flask.abort(403, 'Mauvaise signature')

        response = user_pb2.AuthResponse()
        user_dict = self._db.user.find_one({'facebookId': data['user_id']})
        user_id = str(user_dict['_id']) if user_dict else ''
        if proto.parse_from_mongo(user_dict, response.authenticated_user):
            response.authenticated_user.user_id = user_id
        else:
            response.authenticated_user.facebook_id = data['user_id']
            self._save_new_user(response.authenticated_user)
            response.is_new_user = True

        return response
Ejemplo n.º 2
0
    def _google_authenticate(self, token_id):
        try:
            id_info = client.verify_id_token(token_id, GOOGLE_SSO_CLIENT_ID)
        except crypt.AppIdentityError as error:
            flask.abort(401, "Mauvais jeton d'authentification : %s" % error)
        if id_info.get('iss') not in _GOOGLE_SSO_ISSUERS:
            flask.abort(
                401, "Fournisseur d'authentification invalide : %s." %
                id_info.get('iss', '<none>'))

        response = user_pb2.AuthResponse()
        user_dict = self._db.user.find_one({'googleId': id_info['sub']})
        user_id = str(user_dict['_id']) if user_dict else ''
        if proto.parse_from_mongo(user_dict, response.authenticated_user):
            response.authenticated_user.user_id = user_id
        else:
            self._assert_user_not_existing(id_info['email'])
            response.authenticated_user.profile.email = id_info['email']
            response.authenticated_user.profile.picture_url = id_info.get(
                'picture', '')
            response.authenticated_user.google_id = id_info['sub']
            self._save_new_user(response.authenticated_user)
            response.is_new_user = True

        return response
Ejemplo n.º 3
0
    def _linked_in_authenticate(self, code):
        token_data = _get_oauth2_access_token(
            'https://www.linkedin.com/oauth/v2/accessToken',
            code=code,
            client_id=_LINKED_IN_CLIENT_ID,
            client_secret=_LINKED_IN_CLIENT_SECRET,
            auth_name='LinkedIn Auth',
        )

        authorization_header = '{} {}'.format(
            token_data.get('token_type', 'Bearer'),
            token_data.get('access_token', ''))

        user_info_response = requests.get(
            'https://api.linkedin.com/v1/people/~:'
            '(id,location,first-name,last-name,email-address)?format=json',
            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 = user_info_response.json()

        response = user_pb2.AuthResponse()
        # TODO(cyrille): Factorize with other 3rd party auth.
        user_dict = self._user_db.user.find_one(
            {'linkedInId': user_info['id']})
        user_id = str(user_dict.pop('_id')) if user_dict else ''
        if proto.parse_from_mongo(user_dict, response.authenticated_user):
            response.authenticated_user.user_id = user_id
            self._handle_returning_user(response)
        else:
            email = user_info.get('emailAddress')
            if email:
                self._assert_user_not_existing(email)
                response.authenticated_user.profile.email = email
            user = response.authenticated_user
            user.linked_in_id = user_info['id']
            # TODO(pascal): Handle the case where one of the name is missing.
            user.profile.name = user_info.get('firstName', '')
            user.profile.last_name = user_info.get('lastName', '')
            self._save_new_user(user)
            response.is_new_user = True

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

        return response
Ejemplo n.º 4
0
    def _google_authenticate(self, token_id):
        id_info = decode_google_id_token(token_id)
        response = user_pb2.AuthResponse()
        user_dict = self._db.user.find_one({'googleId': id_info['sub']})
        user_id = str(user_dict['_id']) if user_dict else ''
        if proto.parse_from_mongo(user_dict, response.authenticated_user):
            response.authenticated_user.user_id = user_id
        else:
            self._assert_user_not_existing(id_info['email'])
            response.authenticated_user.profile.email = id_info['email']
            response.authenticated_user.profile.picture_url = id_info.get('picture', '')
            response.authenticated_user.google_id = id_info['sub']
            self._save_new_user(response.authenticated_user)
            response.is_new_user = True

        # TODO(benoit): Check token provide enough guarantee for this use case.
        response.auth_token = create_token(response.authenticated_user.user_id, 'auth')

        return response
Ejemplo n.º 5
0
    def _token_authenticate(self, auth_request):
        now = int(time.time())
        response = user_pb2.AuthResponse()
        response.hash_salt = _timestamped_hash(now, auth_request.email)

        try:
            user_id = objectid.ObjectId(auth_request.user_id)
        except objectid.InvalidId:
            flask.abort(
                400, 'L\'identifiant utilisateur "{}" n\'a pas le bon format.'.
                format(auth_request.user_id))
        user_dict = self._user_db.user.find_one({'_id': user_id})

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

        try:
            if not _assert_valid_salt(auth_request.auth_token,
                                      str(user_id),
                                      now,
                                      validity_seconds=datetime.timedelta(
                                          days=5).total_seconds()):
                flask.abort(403, "Token d'authentification périmé")
        except ValueError as error:
            flask.abort(
                403,
                "Le sel n'a pas été généré par ce serveur : {}.".format(error))

        if not proto.parse_from_mongo(user_dict, response.authenticated_user):
            flask.abort(
                500,
                'Les données utilisateur sont corrompues dans la base de données.'
            )

        response.auth_token = create_token(str(user_id), 'auth')
        response.authenticated_user.user_id = str(user_id)
        self._handle_returning_user(response)

        return response
Ejemplo n.º 6
0
    def _password_authenticate(self, auth_request):
        now = int(time.time())
        response = user_pb2.AuthResponse()
        response.hash_salt = _timestamped_hash(now, auth_request.email)

        user_dict = self._user_db.user.find_one(
            {'profile.email': auth_request.email})

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

        user_id = str(user_dict['_id'])
        response.auth_token = create_token(user_id, 'auth')

        user_auth_dict = self._user_db.user_auth.find_one(
            {'_id': user_dict['_id']})

        if not user_auth_dict:
            if user_dict.get('googleId'):
                auth_method = ' (Google)'
            elif user_dict.get('facebookId'):
                auth_method = ' (Facebook)'
            else:
                auth_method = ''
            flask.abort(
                403,
                "L'utilisateur existe mais utilise un autre moyen de connexion{}."
                .format(auth_method))

        if not auth_request.hashed_password:
            # User exists but did not sent a passwordt: probably just getting some fresh salt.
            return response

        if auth_request.auth_token:
            self._reset_password(auth_request, user_id, user_auth_dict)
            user_dict['userId'] = user_id
            if not proto.parse_from_mongo(user_dict,
                                          response.authenticated_user):
                flask.abort(
                    500,
                    'Les données utilisateur sont corrompues dans la base de données.'
                )
            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 _assert_valid_salt(salt, auth_request.email, now):
                return response
        except ValueError as error:
            flask.abort(
                403,
                "Le sel n'a pas été généré par ce serveur : {}.".format(error))

        stored_hashed_password = user_auth_dict.get('hashedPassword')

        hashed_password = hashlib.sha1()
        hashed_password.update(salt.encode('ascii'))
        hashed_password.update(stored_hashed_password.encode('ascii'))
        request_hashed_password = binascii.unhexlify(
            auth_request.hashed_password)
        if request_hashed_password != hashed_password.digest():
            flask.abort(403, 'Mot de passe erroné.')

        if not proto.parse_from_mongo(user_dict, response.authenticated_user):
            flask.abort(
                500,
                'Les données utilisateur sont corrompues dans la base de données.'
            )

        response.authenticated_user.user_id = user_id
        self._handle_returning_user(response)

        return response
Ejemplo n.º 7
0
    def _pe_connect_authenticate(self, code, nonce):
        token_data = _get_oauth2_access_token(
            'https://authentification-candidat.pole-emploi.fr/connexion/oauth2/access_token?'
            'realm=/individu',
            code=code,
            client_id=_EMPLOI_STORE_CLIENT_ID,
            client_secret=_EMPLOI_STORE_CLIENT_SECRET,
            auth_name='PE Connect',
        )

        if token_data.get('nonce') != nonce:
            flask.abort(403, 'Mauvais paramètre nonce')
        authorization_header = '{} {}'.format(
            token_data.get('token_type', 'Bearer'),
            token_data.get('access_token', ''))
        scopes = token_data.get('scope', '').split(' ')

        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 = user_info_response.json()

        city = None
        if 'coordonnees' in scopes:
            coordinates_response = requests.get(
                'https://api.emploi-store.fr/partenaire/peconnect-coordonnees/v1/coordonnees',
                headers={
                    'Authorization': authorization_header,
                    'pe-nom-application': 'Bob Emploi',
                })
            if coordinates_response.status_code >= 200 and coordinates_response.status_code < 400:
                coordinates = coordinates_response.json()
                city = geo.get_city_proto(coordinates.get('codeINSEE'))

        job = None
        if 'competences' in scopes:
            competences_response = requests.get(
                'https://api.emploi-store.fr/partenaire/peconnect-competences/v1/competences',
                headers={'Authorization': authorization_header},
            )
            if competences_response.status_code >= 200 and competences_response.status_code < 400:
                competences = competences_response.json()
                job_id, rome_id = next(
                    ((c.get('codeAppellation'), c.get('codeRome'))
                     for c in competences), (None, None))
                job = jobs.get_job_proto(self._db, job_id, rome_id)

        response = user_pb2.AuthResponse()
        user_dict = self._user_db.user.find_one(
            {'peConnectId': user_info['sub']})
        user_id = str(user_dict.pop('_id')) if user_dict else ''
        if proto.parse_from_mongo(user_dict, response.authenticated_user):
            response.authenticated_user.user_id = user_id
            self._handle_returning_user(response)
        else:
            email = user_info.get('email')
            if email:
                self._assert_user_not_existing(email)
                response.authenticated_user.profile.email = email
            user = response.authenticated_user
            user.pe_connect_id = user_info['sub']
            # 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_pb2.UNKNOWN_GENDER)
            if city or job:
                user.projects.add(
                    is_incomplete=True,
                    mobility=geo_pb2.Location(city=city) if city else None,
                    target_job=job)
            self._save_new_user(user)
            response.is_new_user = True

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

        return response