Beispiel #1
0
    def maybe_generate_error(self, rate):
        """
        Generate an error according to login and random rate.

        If login is a specific login matching an error type
        (``invalidpassword``, ``actionneeded``, ``expiredpassword``),
        systematically trigger the matching error. If login is ``noerror``,
        never throw any error. In all other cases, throw a random error
        according to given rate.

        :param rate: Rate at which the errors should be generated.
        """
        login = self.config['login'].get()

        if login == 'invalidpassword':
            raise BrowserIncorrectPassword
        if login == 'actionneeded':
            raise ActionNeeded
        if login == 'expiredpassword':
            raise BrowserPasswordExpired
        if login == 'authmethodnotimplemented':
            raise AuthMethodNotImplemented
        if login == 'appvalidation' and self.config.get('resume',
                                                        Value()).get() is None:
            raise AppValidation("Please confirm login!")
        if login == '2fa' and self.config.get('code', Value()).get() is None:
            raise BrowserQuestion(
                Value('code', label="Please enter some fake 2fa code!"))
        if login not in ['noerror', 'session', 'appvalidation', '2fa']:
            self.random_errors(rate)
Beispiel #2
0
    def do_login(self):
        if self.config['pin_code'].get():
            self.validate_security_form()

            if not self.page.is_logged():
                raise BrowserIncorrectPassword(
                    "Login / Password or authentication pin_code incorrect")
            else:
                return

        self.login.go()

        if self.page.is_logged():
            return

        self.page.login(self.username, self.password)

        self.page.check_user_double_auth()

        if self.page.check_website_double_auth():
            self.otp_form = self.page.get_security_form()
            self.otp_url = self.url

            raise BrowserQuestion(
                Value('pin_code',
                      label=self.page.get_otp_message()[0]
                      or 'Please type the OTP you received'))

        if not self.page.is_logged():
            raise BrowserIncorrectPassword
Beispiel #3
0
    def on_load(self):
        receive_code_btn = bool(
            self.doc.xpath(
                '//div[has-class("authentification-bloc-content-btn-bloc")][count(input)=1]'
            ))
        submit_input = self.doc.xpath('//input[@type="submit"]')
        if receive_code_btn and len(submit_input) == 1:
            form = self.get_form(
                xpath=
                '//form[.//div[has-class("authentification-bloc-content-btn-bloc")][count(input)=1]]',
                submit=
                '//div[has-class("authentification-bloc-content-btn-bloc")]//input[@type="submit"]'
            )

            # sending mail with code
            form.submit()
            raise BrowserQuestion(
                Value('otp', label=u'Veuillez saisir votre code de sécurité'))

        send_code_form = bool(
            self.doc.xpath(
                '//form[.//div[has-class("authentification-bloc-content-btn-bloc")]]'
            ))
        # TODO move this code in browser
        otp = self.browser.config['otp'].get(
        ) if 'otp' in self.browser.config else None
        if send_code_form and otp:
            self.check_error()
            self.send_otp(otp)
Beispiel #4
0
    def sms_second_step(self):
        form = self.get_form()
        self.browser.auth_token = form['flow_secureForm_instance']
        form['otp_prepare[receiveCode]'] = ''
        form.submit()

        raise BrowserQuestion(Value('pin_code', label='Enter the PIN Code'))
Beispiel #5
0
    def send_sms(self):
        """This function simulates the registration of a device on
        boursorama two factor authentification web page.
        I
        @param device device name to register
        @exception BrowserAuthenticationCodeMaxLimit when daily limit is consumed
        """
        url = "https://%s/ajax/banque/otp.phtml?org=%s&alertType=10100" % (
            self.browser.DOMAIN, self.REFERER)
        req = urllib2.Request(url, headers=self.headers_ajax)
        response = self.browser.open(req)
        #extrat authentication token from response (in form)
        info = response.read()

        regex = re.compile(self.MAX_LIMIT)
        r = regex.search(info)
        if r:
            raise BrowserAuthenticationCodeMaxLimit(
                "Vous avez atteint le nombre maximum d'utilisation de l'authentification forte"
            )

        regex = re.compile(r"name=\\\"authentificationforteToken\\\" "
                           r"value=\\\"(?P<value>\w*?)\\\"")
        r = regex.search(info)
        self.browser.auth_token = r.group('value')

        #step2
        url = "https://" + self.browser.DOMAIN + "/ajax/banque/otp.phtml"
        data = "authentificationforteToken=%s&authentificationforteStep=start&alertType=10100&org=%s&validate=" % (
            self.browser.auth_token, self.REFERER)
        req = urllib2.Request(url, data, self.headers_ajax)
        response = self.browser.open(req)
        raise BrowserQuestion(Value('pin_code', label='Enter the PIN Code'))
Beispiel #6
0
    def do_login(self):
        self.code = self.config['code'].get()
        if self.resume:
            return self.handle_polling()
        elif self.code:
            return self.handle_sms()

        self.login_without_2fa()

        self.auth_page.go()
        if self.auth_page.is_here():
            # Handle 2FA
            # 2FA seem to be handled by LBP. Indeed logins after 2FA will redirect to the LBP main page
            # Consequently no state for future connexion needs to be kept
            if self.request_information is None:
                raise NeedInteractiveFor2FA()
            auth_method = self.page.get_auth_method()

            if auth_method == 'cer+':
                # We force here the first device present
                self.decoupled_page.go(params={'deviceSelected': '0'})
                raise AppValidation(self.page.get_decoupled_message())

            elif auth_method == 'cer':
                self.location('/voscomptes/canalXHTML/securite/gestionAuthentificationForte/authenticateCerticode-gestionAuthentificationForte.ea')
                self.page.check_if_is_blocked()
                self.sms_form = self.page.get_sms_form()
                raise BrowserQuestion(Value('code', label='Entrez le code reçu par SMS'))

            elif auth_method == 'no2fa':
                self.location(self.page.get_skip_url())
Beispiel #7
0
    def handle_security(self):
        if self.page.doc.xpath('//span[@class="a-button-text"]'):
            self.page.send_code()
            self.otp_form = self.page.get_response_form()
            self.otp_url = self.url

            raise BrowserQuestion(
                Value('pin_code',
                      label=self.page.get_otp_message()
                      if self.page.get_otp_message() else
                      'Please type the OTP you received'))
Beispiel #8
0
    def sms_second_step(self):
        # <div class="form-errors"><ul><li>Vous avez atteint le nombre maximal de demandes pour aujourd&#039;hui</li></ul></div>
        error = CleanText('//div[has-class("form-errors")]')(self.doc)
        if len(error) > 0:
            raise BrowserIncorrectPassword(error)

        form = self.get_form()
        self.browser.auth_token = form['flow_secureForm_instance']
        form['otp_prepare[receiveCode]'] = ''
        form.submit()

        raise BrowserQuestion(Value('pin_code', label='Enter the PIN Code'))
Beispiel #9
0
    def check_auth_methods(self):
        if self.mobile_confirmation.is_here():
            self.page.check_bypass()
            if self.mobile_confirmation.is_here():
                self.polling_data = self.page.get_polling_data()
                assert self.polling_data, "Can't proceed to polling if no polling_data"
                raise AppValidation(self.page.get_validation_msg())

        if self.otp_validation_page.is_here():
            self.otp_data = self.page.get_otp_data()
            assert self.otp_data, "Can't proceed to SMS handling if no otp_data"
            raise BrowserQuestion(Value('code', label=self.page.get_message()))

        self.check_otp_blocked()
Beispiel #10
0
 def do_otp(self, mfaToken):
     data = {
         'challengeType': 'otp',
         'mfaToken': mfaToken
     }
     try:
         result = self.request('/api/mfa/challenge', json=data)
     except ClientError as e:
         json_response = e.response.json()
         # if we send more than 5 otp without success, the server will warn the user to
         # wait 12h before retrying, but in fact it seems that we can resend otp 5 mins later
         if e.response.status_code == 429:
             raise BrowserUnavailable(json_response['detail'])
     raise BrowserQuestion(Value('otp', label='Veuillez entrer le code reçu par sms au ' + result['obfuscatedPhoneNumber']))
Beispiel #11
0
    def handle_sms(self):
        self.otp_data['final_url_params']['otp_password'] = self.code
        self.finalize_twofa(self.otp_data)

        ## cases where 2FA is not finalized
        # Too much wrong OTPs, locked down after total 3 wrong inputs
        self.check_otp_blocked()

        # OTP is expired after 15', we end up on login page
        if self.login.is_here():
            raise BrowserIncorrectPassword("Le code de confirmation envoyé par SMS n'est plus utilisable")

        # Wrong OTP leads to same form with error message, re-raise BrowserQuestion
        elif self.otp_validation_page.is_here():
            error_msg = self.page.get_error_message()
            if 'erroné' not in error_msg:
                raise BrowserUnavailable(error_msg)
            else:
                label = '%s %s' % (error_msg, self.page.get_message())
                raise BrowserQuestion(Value('code', label=label))

        self.otp_data = {}
Beispiel #12
0
    def handle_security(self):
        otp_type = self.page.get_otp_type()
        if otp_type == '/ap/signin':
            # this otp will be always present until user deactivate it
            raise ActionNeeded(
                'You have enabled otp in your options, please deactivate it before synchronize'
            )

        if self.page.doc.xpath('//span[@class="a-button-text"]'):
            self.page.send_code()

            form = self.page.get_response_form()
            self.otp_form = form['form']
            self.otp_url = self.url
            self.otp_style = form['style']
            self.otp_headers = dict(self.session.headers)

            raise BrowserQuestion(
                Value('pin_code',
                      label=self.page.get_otp_message()
                      if self.page.get_otp_message() else
                      'Please type the OTP you received'))
Beispiel #13
0
    def do_login(self):
        # ********** admire how login works on edf par website **********
        # login part on edf particulier website is very tricky
        # FIRST time we connect we have an otp, BUT not password, we can't know if it is wrong at this moment
        # SECOND time we use password, and not otp
        auth_params = {'realm': '/INTERNET'}

        if self.config['otp'].get():
            self.otp_data['callbacks'][0]['input'][0]['value'] = self.config[
                'otp'].get()
            headers = {
                'X-Requested-With': 'XMLHttpRequest',
            }
            self.authenticate.go(json=self.otp_data,
                                 params=auth_params,
                                 headers=headers)
            self.id_token1 = self.page.get_data(
            )['callbacks'][1]['output'][0]['value']
            # id_token1 is VERY important, we keep it indefinitely, without it edf will ask again otp
        else:
            self.location('/bin/edf_rc/servlets/sasServlet',
                          params={'processus': 'TDB'})
            if self.connected.is_here():
                # we are already logged
                # sometimes even if password is wrong, you can be logged if you retry
                self.logger.info('already logged')
                return

            self.authenticate.go(method='POST', params=auth_params)
            data = self.page.get_data()
            data['callbacks'][0]['input'][0]['value'] = self.username

            self.authenticate.go(json=data, params=auth_params)
            data = self.page.get_data(
            )  # yes, we have to get response and send it again, beautiful isn't it ?
            if data['stage'] == 'UsernameAuth2':
                # username is wrong
                raise BrowserIncorrectPassword(
                    data['callbacks'][1]['output'][0]['value'])

            if self.id_token1:
                data['callbacks'][0]['input'][0]['value'] = self.id_token1
            else:
                # the FIRST time we connect, we don't have id_token1, we have no choice, we'll receive an otp
                data['callbacks'][0]['input'][0]['value'] = ' '

            self.authenticate.go(json=data, params=auth_params)
            data = self.page.get_data()

            assert data['stage'] in (
                'HOTPcust3', 'PasswordAuth2'), 'stage is %s' % data['stage']

            if data['stage'] == 'HOTPcust3':  # OTP part
                if self.id_token1:
                    # this shouldn't happen except if id_token1 expire one day, who knows...
                    self.logger.warning(
                        'id_token1 is not null but edf ask again for otp')

                # a legend say this url is the answer to life the universe and everything, because it is use EVERYWHERE in login
                self.authenticate.go(json=self.page.get_data(),
                                     params=auth_params)
                self.otp_data = self.page.get_data()
                label = self.otp_data['callbacks'][0]['output'][0]['value']
                raise BrowserQuestion(Value('otp', label=label))

            if data['stage'] == 'PasswordAuth2':  # password part
                data['callbacks'][0]['input'][0]['value'] = self.password
                self.authenticate.go(json=self.page.get_data(),
                                     params=auth_params)

                # should be SetPasAuth2 if password is ok
                if self.page.get_data()['stage'] == 'PasswordAuth2':
                    attempt_number = self.page.get_data(
                    )['callbacks'][1]['output'][0]['value']
                    # attempt_number is the number of wrong password
                    msg = self.wrong_password.go().get_wrongpass_message(
                        attempt_number)
                    raise BrowserIncorrectPassword(msg)

        data = self.page.get_data()
        # yes, send previous data again, i know i know
        self.authenticate.go(json=data, params=auth_params)
        self.session.cookies['ivoiream'] = self.page.get_token()
        self.user_status.go()
        """
        call check_authenticate url before get subscription in profil, or we'll get an error 'invalid session'
        we do nothing with this response (which contains false btw)
        but edf website expect we call it before or will reject us
        """
        self.check_authenticate.go()
Beispiel #14
0
    def get_parcel_tracking(self, _id):
        """
        Get information about a parcel.

        :param _id: _id of the parcel
        :type _id: :class:`str`
        :rtype: :class:`Parcel`
        :raises: :class:`ParcelNotFound`
        """
        # Tracking number format:
        # - 2 chars: optional merchant identifier (eg, AM for Amazon, 85 for cdiscount, ...)
        # - 10 digits: shipment tracking number
        # - 2 digits: optional suffix, seems to always be "01" when present but is never sent to the API
        #
        # Many merchants seem to give only the 10 digits tracking number so the user needs to
        # manually select the merchant from a list in that case.

        merchant = None
        code = None

        _id = _id.strip().upper()

        if len(_id) == 10:
            code = _id
        elif len(_id) in (12, 14):
            merchant = _id[:2]
            code = _id[2:12]
        else:
            raise ParcelNotFound(
                "Tracking number must be 10, 12 or 14 characters long.")

        merchant = merchant or self.config['merchant'].get()

        if not merchant:
            # No merchant info in the tracking number
            # we have to ask the user to select it
            merchants = self.browser.get_merchants()
            raise BrowserQuestion(
                Value(
                    'merchant',
                    label='Merchant prefix (prepend to tracking number): ',
                    tiny=False,
                    choices=merchants,
                ))

        self.config['merchant'].set(None)
        name = self.config['last_name'].get()[:4].ljust(4).upper()

        events = list(self.browser.iter_events(merchant, code, name))

        parcel = Parcel(merchant + code)
        parcel.arrival = NotAvailable

        # This is what the legacy tracking website used to show
        # when there are no events yet
        parcel.info = "Votre commande est en cours d'acheminement dans notre réseau."

        parcel.history = events
        parcel.status = Parcel.STATUS_IN_TRANSIT

        if not events:
            parcel.status = Parcel.STATUS_PLANNED
            return parcel

        parcel.info = events[0].activity

        arrived_event = next(
            (event for event in events
             if "Votre colis est disponible" in event.activity), None)

        if arrived_event:
            parcel.status = Parcel.STATUS_ARRIVED
            parcel.arrival = arrived_event.date

        return parcel
Beispiel #15
0
    def check_auth_method(self):
        auth_method = self.page.get_auth_method()

        if not auth_method:
            self.logger.warning('No auth method available !')
            raise ActionNeeded(
                'Veuillez ajouter un numéro de téléphone sur votre banque et/ou activer votre Pass Sécurité'
            )

        if auth_method['unavailability_reason'] == "ts_non_enrole":
            raise ActionNeeded(
                'Veuillez ajouter un numéro de téléphone sur votre banque')

        elif auth_method['unavailability_reason']:
            assert False, 'Unknown unavailability reason "%s" found' % auth_method[
                'unavailability_reason']

        if auth_method['type_proc'].lower() == 'auth_oob':
            self.location(
                '/sec/oob_sendooba.json',
                method='POST',
                headers={'Content-Type': 'application/x-www-form-urlencoded'},
            )

            donnees = self.page.doc['donnees']
            self.polling_transaction = donnees['id-transaction']

            if donnees.get('expiration_date_hh') and donnees.get(
                    'expiration_date_mm'):
                now = datetime.now()
                expiration_date = now.replace(
                    hour=int(donnees['expiration_date_hh']),
                    minute=int(donnees['expiration_date_mm']))
                self.polling_duration = int(
                    (expiration_date - now).total_seconds())

            message = "Veuillez valider l'opération dans votre application"
            terminal_name = auth_method['terminal'][0]['nom']
            if terminal_name:
                message += " sur " + terminal_name

            raise AppValidation(message)

        elif auth_method['type_proc'].lower() == 'auth_csa':
            if auth_method['mode'] == "SMS":
                self.location('/sec/csa/send.json', data={
                    'csa_op': "auth",
                })
                raise BrowserQuestion(
                    Value(
                        'code',
                        label=
                        'Entrez le Code Sécurité reçu par SMS sur le numéro ' +
                        auth_method['ts']))

            self.logger.warning('Unknown CSA method "%s" found',
                                auth_method['mod'])

        else:
            self.logger.warning('Unknown sign method "%s" found',
                                auth_method['type_proc'])

        assert False, 'Unknown auth method "%s: %s" found' % (
            auth_method['type_proc'], auth_method.get('mod'))