Login in portal SAT
This commit is contained in:
parent
da889c9af5
commit
4ee0aabbff
|
@ -38,6 +38,7 @@ class SATCertificate(object):
|
||||||
self._cer_modulus = 0
|
self._cer_modulus = 0
|
||||||
self._key_modulus = 0
|
self._key_modulus = 0
|
||||||
self._issuer = ''
|
self._issuer = ''
|
||||||
|
self._fert = ''
|
||||||
return
|
return
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -62,6 +63,7 @@ class SATCertificate(object):
|
||||||
self._not_before = obj.not_valid_before
|
self._not_before = obj.not_valid_before
|
||||||
self._not_after = obj.not_valid_after
|
self._not_after = obj.not_valid_after
|
||||||
self._issuer = ','.join([i.rfc4514_string() for i in obj.issuer])
|
self._issuer = ','.join([i.rfc4514_string() for i in obj.issuer])
|
||||||
|
self._fert = self._not_after.strftime('%y%m%d%H%M%SZ')
|
||||||
|
|
||||||
now = datetime.datetime.utcnow()
|
now = datetime.datetime.utcnow()
|
||||||
self._is_valid_time = (now > self.not_before) and (now < self.not_after)
|
self._is_valid_time = (now > self.not_before) and (now < self.not_after)
|
||||||
|
@ -123,6 +125,10 @@ class SATCertificate(object):
|
||||||
def not_after(self):
|
def not_after(self):
|
||||||
return self._not_after
|
return self._not_after
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fert(self):
|
||||||
|
return self._fert
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_fiel(self):
|
def is_fiel(self):
|
||||||
return self._is_fiel
|
return self._is_fiel
|
||||||
|
@ -215,4 +221,4 @@ class SATCertificate(object):
|
||||||
sign = private_key.sign(data.encode(), padding.PKCS1v15(), type_hash)
|
sign = private_key.sign(data.encode(), padding.PKCS1v15(), type_hash)
|
||||||
del password
|
del password
|
||||||
del private_key
|
del private_key
|
||||||
return base64.b64encode(sign).decode()
|
return base64.b64encode(sign)
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import base64
|
||||||
|
import logging
|
||||||
|
import ssl
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import lxml.html
|
||||||
|
|
||||||
|
|
||||||
|
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
LOG_DATE = '%d/%m/%Y %H:%M:%S'
|
||||||
|
logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m')
|
||||||
|
logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m')
|
||||||
|
logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m')
|
||||||
|
logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE)
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
BROWSER = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/109.0'
|
||||||
|
HOST = 'cfdiau.sat.gob.mx'
|
||||||
|
REFERER = 'https://cfdiau.sat.gob.mx/nidp/app/login?id=SATUPCFDiCon&sid=0&option=credential&sid=0'
|
||||||
|
URL_MAIN = 'https://portalcfdi.facturaelectronica.sat.gob.mx/'
|
||||||
|
URL = {
|
||||||
|
'LOGIN': 'https://cfdiau.sat.gob.mx/nidp/app/login?id=SATx509Custom&sid=0&option=credential&sid=0',
|
||||||
|
'CONSULTA': URL_MAIN + 'Consulta.aspx',
|
||||||
|
'LOGOUT': URL_MAIN + 'logout.aspx?salir=y',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ssl_context = ssl.create_default_context()
|
||||||
|
ssl_context.set_ciphers('HIGH:!DH:!aNULL')
|
||||||
|
|
||||||
|
|
||||||
|
async def _get(client, url, headers={}):
|
||||||
|
async with client.get(url, headers=headers, ssl=ssl_context) as r:
|
||||||
|
# ~ assert r.status == 200
|
||||||
|
return await r.text()
|
||||||
|
|
||||||
|
|
||||||
|
async def _post(client, url, data={}, headers={}):
|
||||||
|
async with client.post(url, data=data, headers=headers, ssl=ssl_context) as r:
|
||||||
|
# ~ assert r.status == 200
|
||||||
|
return await r.text()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_headers(host, referer, ajax=False):
|
||||||
|
headers = {
|
||||||
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||||
|
'Accept-Encoding': 'gzip, deflate, br',
|
||||||
|
'Accept-Language': 'en-US,en;q=0.5',
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
'DNT': '1',
|
||||||
|
'Host': host,
|
||||||
|
'Referer': referer,
|
||||||
|
'Upgrade-Insecure-Requests': '1',
|
||||||
|
'User-Agent': BROWSER,
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
}
|
||||||
|
if ajax:
|
||||||
|
headers.update({
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'X-MicrosoftAjax': 'Delta=true',
|
||||||
|
'x-requested-with': 'XMLHttpRequest',
|
||||||
|
'Pragma': 'no-cache',
|
||||||
|
})
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
def _get_data_form(html):
|
||||||
|
tree = lxml.html.fromstring(html)
|
||||||
|
data = {}
|
||||||
|
for tag in tree.xpath("//input[@type='hidden']"):
|
||||||
|
if 'name' in tag.attrib and 'value' in tag.attrib:
|
||||||
|
data[tag.attrib['name']] = tag.attrib['value']
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _get_post_type_search(html, emitidas=False):
|
||||||
|
tipo_busqueda = 'RdoTipoBusquedaReceptor'
|
||||||
|
if emitidas:
|
||||||
|
tipo_busqueda = 'RdoTipoBusquedaEmisor'
|
||||||
|
sm = 'ctl00$MainContent$UpnlBusqueda|ctl00$MainContent$BtnBusqueda'
|
||||||
|
|
||||||
|
post = _get_data_form(html)
|
||||||
|
post['ctl00$MainContent$TipoBusqueda'] = tipo_busqueda
|
||||||
|
post['__ASYNCPOST'] = 'true'
|
||||||
|
post['__EVENTTARGET'] = ''
|
||||||
|
post['__EVENTARGUMENT'] = ''
|
||||||
|
post['ctl00$ScriptManager1'] = sm
|
||||||
|
return post
|
||||||
|
|
||||||
|
|
||||||
|
async def _login(client, cert):
|
||||||
|
msg = 'Init login...'
|
||||||
|
log.info(msg)
|
||||||
|
|
||||||
|
async with client.get(URL_MAIN, ssl=ssl_context) as response:
|
||||||
|
url = str(response.url)
|
||||||
|
|
||||||
|
headers = {'Host': 'cfdiau.sat.gob.mx'}
|
||||||
|
html = await _get(client, url, headers)
|
||||||
|
|
||||||
|
headers = {'User-Agent': BROWSER, 'Referer': url}
|
||||||
|
html = await _post(client, URL['LOGIN'], headers=headers)
|
||||||
|
|
||||||
|
tree = lxml.html.fromstring(html)
|
||||||
|
values = {}
|
||||||
|
for tag in tree.xpath('//input'):
|
||||||
|
if 'id' in tag.attrib and 'value' in tag.attrib:
|
||||||
|
values[tag.attrib['id']] = tag.attrib['value']
|
||||||
|
|
||||||
|
co = f"{values['tokenuuid']}|{cert.rfc}|{cert.serial_number_str}"
|
||||||
|
firma = base64.b64encode(cert.sign(co)).decode('utf-8')
|
||||||
|
co = base64.b64encode(co.encode('utf-8')).decode('utf-8')
|
||||||
|
data = '{}#{}'.format(co, firma).encode('utf-8')
|
||||||
|
token = base64.b64encode(data).decode('utf-8')
|
||||||
|
|
||||||
|
keys = ('credentialsRequired', 'guid', 'ks', 'urlApplet')
|
||||||
|
data = {k: values[k] for k in keys}
|
||||||
|
data['fert'] = cert.fert
|
||||||
|
data['token'] = token
|
||||||
|
|
||||||
|
headers = _get_headers(HOST, REFERER)
|
||||||
|
html = await _post(client, URL['LOGIN'], data, headers)
|
||||||
|
|
||||||
|
if not html:
|
||||||
|
msg = 'Error al identificarse en el SAT'
|
||||||
|
log.error(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
data = _get_data_form(html)
|
||||||
|
|
||||||
|
html = await _post(client, URL_MAIN, data)
|
||||||
|
data = _get_post_type_search(html)
|
||||||
|
headers = _get_headers(HOST, URL_MAIN)
|
||||||
|
|
||||||
|
html = await _post(client, URL['CONSULTA'], data, headers)
|
||||||
|
|
||||||
|
msg = '\tLogin Ok...'
|
||||||
|
log.info(msg)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
async def _logout(client):
|
||||||
|
html = await _get(client, URL['LOGOUT'])
|
||||||
|
msg = 'Logout Ok...'
|
||||||
|
log.info(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
async def _search(client, data):
|
||||||
|
print(data)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
async def _main(data, cert):
|
||||||
|
async with aiohttp.ClientSession() as client:
|
||||||
|
await _login(client, cert)
|
||||||
|
await _search(client, data)
|
||||||
|
await _logout(client)
|
||||||
|
|
||||||
|
|
||||||
|
def portal_sat(data, cert):
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(_main(data, cert))
|
||||||
|
return
|
|
@ -6,6 +6,7 @@ import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .cfdi_cert import SATCertificate
|
from .cfdi_cert import SATCertificate
|
||||||
|
from .sat import portal_sat
|
||||||
|
|
||||||
|
|
||||||
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
@ -104,15 +105,16 @@ def _validate_arguments(args):
|
||||||
if cert is None:
|
if cert is None:
|
||||||
return {}, None
|
return {}, None
|
||||||
|
|
||||||
data = {'path_dowload': ''}
|
data = {'ok': True}
|
||||||
|
|
||||||
return data, cert
|
return data, cert
|
||||||
|
|
||||||
|
|
||||||
def download(args):
|
def download(args):
|
||||||
data, cert = _validate_arguments(args)
|
data, cert = _validate_arguments(args)
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
|
|
||||||
print(data)
|
portal_sat(data, cert)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in New Issue