Skip to content

Commit

Permalink
Merge 5107a65 into 717f7fb
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasrcezimbra committed May 5, 2021
2 parents 717f7fb + 5107a65 commit 9591540
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 36 deletions.
55 changes: 19 additions & 36 deletions pyitau/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import requests
from bs4 import BeautifulSoup

from pyitau.pages import HomePage, FirstRouterPage, SecondRouterPage, PasswordPage


ITAU_URL = 'https://www.itau.com.br'
ROUTER_URL = 'https://internetpf2.itau.com.br/router-app/router'
Expand Down Expand Up @@ -76,10 +78,9 @@ def get_statements(self):

def _authenticate0(self):
response = self._session.get(ITAU_URL)
soup = BeautifulSoup(response.text, features='html.parser')
form = soup.find('form', attrs={'name': 'banklineAgConta'})
self._id = form.find('input', attrs={'name': 'portal'}).attrs['value']
self._op1 = form.find('input', attrs={'name': 'tipoLogon'}).attrs['value']
page = HomePage(response.text)
self._id = page.id
self._op1 = page.op

def _authenticate1(self):
data = {
Expand All @@ -105,15 +106,13 @@ def _authenticate2(self):
'destino': '',
}
response = self._session.post(ROUTER_URL, data=data)

auth_token = re.search("authToken=\\'(.*?)\\';", response.text).group(1)
self._session.cookies.set('X-AUTH-TOKEN', auth_token)

self._op2 = re.search("\$SECAPDK.uidap\(\'(.*?)\'\);", response.text).group(1)
self._op3 = re.search("\$SECBCATCH.uidap\(\'(.*)\'\);", response.text).group(1)
self._op4 = re.search('router.performRequest\("(.*?)", ', response.text).group(1)
self._flow_id = re.search("var flowId=\'(.*)\';", response.text).group(1)
self._client_id = re.search("var clientId=\'(.*?)\';", response.text).group(1)
page = FirstRouterPage(response.text)
self._session.cookies.set('X-AUTH-TOKEN', page.auth_token)
self._op2 = page.secapdk
self._op3 = page.secbcatch
self._op4 = page.perform_request
self._flow_id = page.flow_id
self._client_id = page.client_id

def _authenticate3(self):
headers = {
Expand All @@ -133,12 +132,10 @@ def _authenticate4(self):
def _authenticate5(self):
headers = {'op': self._op4}
response = self._session.post(ROUTER_URL, headers=headers)
self._op5 = re.search('__opSignCommand = "(.*?)";', response.text).group(1)
self._op6 = re.search('__opMaquinaPirata = "(.*?)";', response.text).group(1)
self._op7 = re.search(
'var guardiao_cb = function\(\) {\n\t\t\tloadPage\(\'(.*?)\'\);',
response.text
).group(1)
page = SecondRouterPage(response.text)
self._op5 = page.op_sign_command
self._op6 = page.op_maquina_pirata
self._op7 = page.guardiao_cb

def _authenticate6(self):
headers = {'op': self._op5}
Expand All @@ -151,24 +148,10 @@ def _authenticate7(self):
def _authenticate8(self):
headers = {'op': self._op7}
response = self._session.post(ROUTER_URL, headers=headers)
page = PasswordPage(response.text)

soup = BeautifulSoup(response.text, features='html.parser')

self._op8 = soup.find('input', id='op').attrs['value']

keys = soup.find(class_='teclado') \
.find(class_='teclas') \
.findAll(class_='campoTeclado')
password_mapper = {}
for key in keys:
numbers = key.attrs['aria-label'].split(' ou ')
letter = key.attrs['rel'][0].replace('tecla_', '')
password_mapper[numbers[0]] = letter
password_mapper[numbers[1]] = letter

self._letter_password = ''
for char in self.password:
self._letter_password += password_mapper[char]
self._op8 = page.op
self._letter_password = page.letter_password(self.password)

def _authenticate9(self):
headers = {'op': self._op8}
Expand Down
143 changes: 143 additions & 0 deletions pyitau/pages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import re

from bs4 import BeautifulSoup


class HomePage:
"""
Página inicial do Itaú. Contém formulário com Agência e Conta.
Utilizada para extrair informações do formulário de login.
"""
def __init__(self, response_text):
self._soup = BeautifulSoup(response_text, features='html.parser')

@property
def _form(self):
"""
Formulário de login com os campos Agência e Conta
"""
return self._soup.find('form', attrs={'name': 'banklineAgConta'})

@property
def id(self):
"""
Campo id do formulário de Agência/Conta
"""
return self._form.find('input', attrs={'name': 'portal'}).attrs['value']

@property
def op(self):
"""
Campo op do formulário de Agência/Conta
"""
return self._form.find('input', attrs={'name': 'tipoLogon'}).attrs['value']


class FirstRouterPage:
"""
Primeira página após enviar o formulário de Agência e Conta.
Utilizada para extrair do HTML informações que serão necessárias nas
próximas requisições.
"""
def __init__(self, response_text):
self._text = response_text

@property
def auth_token(self):
"""
Token de autenticação utilizado como cookie nas próximas requisições
"""
return re.search("authToken=\\'(.*?)\\';", self._text).group(1)

@property
def client_id(self):
return re.search("var clientId=\'(.*?)\';", self._text).group(1)

@property
def flow_id(self):
return re.search("var flowId=\'(.*)\';", self._text).group(1)

@property
def secapdk(self):
return re.search("\$SECAPDK.uidap\(\'(.*?)\'\);", self._text).group(1)

@property
def secbcatch(self):
return re.search("\$SECBCATCH.uidap\(\'(.*)\'\);", self._text).group(1)

@property
def perform_request(self):
return re.search('router.performRequest\("(.*?)", ', self._text).group(1)


class SecondRouterPage:
"""
Segunda página após enviar o formulário de Agência e Conta.
Também utilizada para extrair do HTML informações que serão necessárias nas
próximas requisições.
"""
def __init__(self, response_text):
self._text = response_text

@property
def op_sign_command(self):
return re.search('__opSignCommand = "(.*?)";', self._text).group(1)

@property
def op_maquina_pirata(self):
return re.search('__opMaquinaPirata = "(.*?)";', self._text).group(1)

@property
def guardiao_cb(self):
return re.search(
'var guardiao_cb = function\(\) {\n\t\t\tloadPage\(\'(.*?)\'\);',
self._text
).group(1)


class PasswordPage:
"""
Página do teclado da senha. Contém 2 dígitos por botão.
Por baixo dos panos cada botão representa uma letra.
A combinação das letras deve ser enviada na próxima requisição.
"""
def __init__(self, response_text):
self._soup = BeautifulSoup(response_text, features='html.parser')

@property
def op(self):
"""
Campo op do formulário de senha
"""
return self._soup.find('input', id='op').attrs['value']

def _get_keys(self):
"""
Retorna lista de elementos HTML A que representam os botões que compõem
o teclado da senha. Cada botão tem 2 dígitos e é representado por 1 letra.
Dígitos e letras mudam a cada tentativa de login.
"""
div_teclado = self._soup.find(class_='teclado')
div_teclas = div_teclado.find(class_='teclas')
return div_teclas.findAll(class_='campoTeclado')

def _get_password_mapper(self):
"""
Retorna de/para do número para letra da senha
"""
mapper = {}

for key in self._get_keys():
numbers = key.attrs['aria-label'].split(' ou ')
letter = key.attrs['rel'][0].replace('tecla_', '')
mapper[numbers[0]] = letter
mapper[numbers[1]] = letter

return mapper

def letter_password(self, password):
"""
Recebe senha de número e retorna em letras do teclado da senha.
"""
mapper = self._get_password_mapper()
return ''.join(mapper[n] for n in password)
38 changes: 38 additions & 0 deletions tests/test_first_router_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import pytest
from bs4 import BeautifulSoup

from pyitau.pages import FirstRouterPage


@pytest.fixture
def page(response_authenticate2):
return FirstRouterPage(response_authenticate2)


def test_init(response_authenticate2):
page = FirstRouterPage(response_authenticate2)
assert page._text == response_authenticate2


def test_auth_token(page):
assert page.auth_token == 'PYITAU_AUTHTOKEN'


def test_client_id(page):
assert page.client_id == 'PYITAU_CLIENTID'


def test_flow_id(page):
assert page.flow_id == 'PYITAU_FLOWID'


def test_secapdk(page):
assert page.secapdk == 'PYITAU_OP2'


def test_secbcatch(page):
assert page.secbcatch == 'PYITAU_OP3'


def test_perform_request(page):
assert page.perform_request == 'PYITAU_OP4'
19 changes: 19 additions & 0 deletions tests/test_home_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import responses
from bs4 import BeautifulSoup

from pyitau.pages import HomePage


def test_init(response_authenticate0):
page = HomePage(response_authenticate0)
assert page._soup == BeautifulSoup(response_authenticate0, features='html.parser')


def test_id(response_authenticate0):
page = HomePage(response_authenticate0)
assert page.id == 'PYITAU_ID_VALUE'


def test_op(response_authenticate0):
page = HomePage(response_authenticate0)
assert page.op == 'PYITAU_OP1_VALUE'
71 changes: 71 additions & 0 deletions tests/test_password_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import pytest
from bs4 import BeautifulSoup

from pyitau.pages import PasswordPage


@pytest.fixture
def page(response_authenticate8):
return PasswordPage(response_authenticate8)


def test_init(response_authenticate8):
page = PasswordPage(response_authenticate8)
assert page._soup == BeautifulSoup(response_authenticate8, features='html.parser')


def test_get_keys(page):
keys = page._get_keys()
assert len(keys) == 5
assert keys == page._soup.find(class_='teclado').find(class_='teclas').findAll(class_='campoTeclado')


def test_password_mapper(page):
page = PasswordPage(
"""
<div class="teclado clearfix">
<div class="teclas clearfix">
<a href="javascript:;" aria-label="1 ou 2" rel="tecla_L" class="tecla left campoTeclado" role="button" >1 ou 2</a>
<a href="javascript:;" aria-label="3 ou 4" rel="tecla_U" class="tecla left campoTeclado" role="button" >3 ou 4</a>
<a href="javascript:;" aria-label="5 ou 6" rel="tecla_C" class="tecla left campoTeclado" role="button" >5 ou 6</a>
<a href="javascript:;" aria-label="7 ou 8" rel="tecla_A" class="tecla left campoTeclado" role="button" >7 ou 8</a>
<a href="javascript:;" aria-label="9 ou 0" rel="tecla_S" class="tecla left campoTeclado" role="button" >9 ou 0</a>
</div>
</div>
"""
)
assert page._get_password_mapper() == {
'1': 'L',
'2': 'L',
'3': 'U',
'4': 'U',
'5': 'C',
'6': 'C',
'7': 'A',
'8': 'A',
'9': 'S',
'0': 'S',
}


def test_letter_password(page):
page = PasswordPage(
"""
<div class="teclado clearfix">
<div class="teclas clearfix">
<a href="javascript:;" aria-label="1 ou 2" rel="tecla_L" class="tecla left campoTeclado" role="button" >1 ou 2</a>
<a href="javascript:;" aria-label="3 ou 4" rel="tecla_U" class="tecla left campoTeclado" role="button" >3 ou 4</a>
<a href="javascript:;" aria-label="5 ou 6" rel="tecla_C" class="tecla left campoTeclado" role="button" >5 ou 6</a>
<a href="javascript:;" aria-label="7 ou 8" rel="tecla_A" class="tecla left campoTeclado" role="button" >7 ou 8</a>
<a href="javascript:;" aria-label="9 ou 0" rel="tecla_S" class="tecla left campoTeclado" role="button" >9 ou 0</a>
</div>
</div>
"""
)
assert page.letter_password('123456') == 'LLUUCC'
assert page.letter_password('135790') == 'LUCASS'
assert page.letter_password('097531') == 'SSACUL'


def test_op(page):
assert page.op == 'PYITAU_OP8'
26 changes: 26 additions & 0 deletions tests/test_second_router_page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import pytest
from bs4 import BeautifulSoup

from pyitau.pages import SecondRouterPage


@pytest.fixture
def page(response_authenticate5):
return SecondRouterPage(response_authenticate5)


def test_init(response_authenticate5):
page = SecondRouterPage(response_authenticate5)
assert page._text == response_authenticate5


def test_op_sign_command(page):
assert page.op_sign_command == 'PYITAU_OP5'


def test_op_maquina_pirata(page):
assert page.op_maquina_pirata == 'PYITAU_OP6'


def test_guardiao_cb(page):
assert page.guardiao_cb == 'PYITAU_OP7'

0 comments on commit 9591540

Please sign in to comment.