From 85c5a37798e4983bba04f2d07babe7e6dca39dc9 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Wed, 30 Dec 2020 22:45:57 -0600 Subject: [PATCH] Refactory class certificates --- requirements.txt | 1 + source/app/controllers/cfdi_cert.py | 256 +++++++++++++ source/app/controllers/comercio/__init__.py | 3 - source/app/controllers/comercio/comercio.py | 342 ------------------ .../app/controllers/comercio/conf.py.example | 40 -- source/app/controllers/configpac.py | 30 +- source/app/controllers/main.py | 16 + source/app/controllers/util.py | 2 +- source/app/controllers/utils.py | 49 ++- source/app/main.py | 3 +- source/app/models/db.py | 7 + source/app/models/main.py | 32 +- source/app/seafileapi/__init__.py | 5 - source/app/seafileapi/admin.py | 7 - source/app/seafileapi/client.py | 77 ---- source/app/seafileapi/exceptions.py | 25 -- source/app/seafileapi/files.py | 250 ------------- source/app/seafileapi/group.py | 22 -- source/app/seafileapi/repo.py | 99 ----- source/app/seafileapi/repos.py | 26 -- source/app/seafileapi/utils.py | 57 --- source/app/settings.py | 5 - source/static/js/controller/admin.js | 280 ++++++++------ source/static/js/ui/admin.js | 7 +- 24 files changed, 513 insertions(+), 1128 deletions(-) create mode 100644 source/app/controllers/cfdi_cert.py delete mode 100644 source/app/controllers/comercio/__init__.py delete mode 100644 source/app/controllers/comercio/comercio.py delete mode 100644 source/app/controllers/comercio/conf.py.example delete mode 100644 source/app/seafileapi/__init__.py delete mode 100644 source/app/seafileapi/admin.py delete mode 100644 source/app/seafileapi/client.py delete mode 100644 source/app/seafileapi/exceptions.py delete mode 100644 source/app/seafileapi/files.py delete mode 100644 source/app/seafileapi/group.py delete mode 100644 source/app/seafileapi/repo.py delete mode 100644 source/app/seafileapi/repos.py delete mode 100644 source/app/seafileapi/utils.py diff --git a/requirements.txt b/requirements.txt index a007210..389a78d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ pypng reportlab psycopg2-binary cryptography +xmlsec # escpos # pyusb diff --git a/source/app/controllers/cfdi_cert.py b/source/app/controllers/cfdi_cert.py new file mode 100644 index 0000000..12b19c4 --- /dev/null +++ b/source/app/controllers/cfdi_cert.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 + +import argparse +import base64 +import datetime +import getpass +from pathlib import Path + +import xmlsec +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography import x509 +from cryptography.x509.oid import NameOID +from cryptography.x509.oid import ExtensionOID +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding + + +from .conf import TOKEN + + +class SATCertificate(object): + + def __init__(self, cer=b'', key=b'', password=''): + self._error = '' + self._init_values() + self._get_data_cer(cer) + self._get_data_key(key, password) + + def _init_values(self): + self._rfc = '' + self._serial_number = '' + self._not_before = None + self._not_after = None + self._is_fiel = False + self._are_couple = False + self._is_valid_time = False + self._cer_pem = '' + self._cer_txt = '' + self._key_enc = b'' + self._p12 = b'' + self._cer_modulus = 0 + self._key_modulus = 0 + return + + def __str__(self): + msg = '\tRFC: {}\n'.format(self.rfc) + msg += '\tNo de Serie: {}\n'.format(self.serial_number) + msg += '\tVálido desde: {}\n'.format(self.not_before) + msg += '\tVálido hasta: {}\n'.format(self.not_after) + msg += '\tEs vigente: {}\n'.format(self.is_valid_time) + msg += '\tSon pareja: {}\n'.format(self.are_couple) + msg += '\tEs FIEL: {}\n'.format(self.is_fiel) + return msg + + def __bool__(self): + return self.is_valid + + def _get_hash(self): + digest = hashes.Hash(hashes.SHA512(), default_backend()) + digest.update(self._rfc.encode()) + digest.update(self._serial_number.encode()) + digest.update(TOKEN.encode()) + return digest.finalize() + + def _get_data_cer(self, cer): + obj = x509.load_der_x509_certificate(cer, default_backend()) + self._rfc = obj.subject.get_attributes_for_oid( + NameOID.X500_UNIQUE_IDENTIFIER)[0].value.split(' ')[0] + self._serial_number = '{0:x}'.format(obj.serial_number)[1::2] + self._not_before = obj.not_valid_before + self._not_after = obj.not_valid_after + now = datetime.datetime.utcnow() + self._is_valid_time = (now > self.not_before) and (now < self.not_after) + if not self._is_valid_time: + msg = 'El certificado no es vigente' + self._error = msg + + self._is_fiel = obj.extensions.get_extension_for_oid( + ExtensionOID.KEY_USAGE).value.key_agreement + + self._cer_pem = obj.public_bytes(serialization.Encoding.PEM).decode() + self._cer_txt = ''.join(self._cer_pem.split('\n')[1:-2]) + self._cer_modulus = obj.public_key().public_numbers().n + return + + def _get_data_key(self, key, password): + self._key_enc = key + if not key or not password: + return + + try: + obj = serialization.load_der_private_key( + key, password.encode(), default_backend()) + except ValueError: + msg = 'La contraseña es incorrecta' + self._error = msg + return + + p = self._get_hash() + self._key_enc = obj.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.BestAvailableEncryption(p) + ) + + self._key_modulus = obj.public_key().public_numbers().n + self._are_couple = self._cer_modulus == self._key_modulus + if not self._are_couple: + msg = 'El CER y el KEY no son pareja' + self._error = msg + return + + def _get_key(self, password): + if not password: + password = self._get_hash() + private_key = serialization.load_pem_private_key( + self._key_enc, password=password, backend=default_backend()) + return private_key + + def _get_key_pem(self): + obj = self._get_key('') + key_pem = obj.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption() + ) + return key_pem + + # Not work + def _get_p12(self): + obj = serialization.pkcs12.serialize_key_and_certificates('test', + self.key_pem, self.cer_pem, None, + encryption_algorithm=serialization.NoEncryption() + ) + return obj + + def sign(self, data, password=''): + private_key = self._get_key(password) + firma = private_key.sign(data, padding.PKCS1v15(), hashes.SHA256()) + return base64.b64encode(firma).decode() + + def sign_xml(self, tree): + node = xmlsec.tree.find_node(tree, xmlsec.constants.NodeSignature) + ctx = xmlsec.SignatureContext() + key = xmlsec.Key.from_memory(self.key_pem, xmlsec.constants.KeyDataFormatPem) + ctx.key = key + ctx.sign(node) + node = xmlsec.tree.find_node(tree, 'X509Certificate') + node.text = self.cer_txt + return tree + + @property + def rfc(self): + return self._rfc + + @property + def serial_number(self): + return self._serial_number + + @property + def not_before(self): + return self._not_before + + @property + def not_after(self): + return self._not_after + + @property + def is_fiel(self): + return self._is_fiel + + @property + def are_couple(self): + return self._are_couple + + @property + def is_valid(self): + return not bool(self.error) + + @property + def is_valid_time(self): + return self._is_valid_time + + @property + def cer_pem(self): + return self._cer_pem.encode() + + @property + def cer_txt(self): + return self._cer_txt + + @property + def key_pem(self): + return self._get_key_pem() + + @property + def key_enc(self): + return self._key_enc + + @property + def p12(self): + return self._get_p12() + + @property + def error(self): + return self._error + + +def main(args): + # ~ contra = getpass.getpass('Introduce la contraseña del archivo KEY: ') + contra = '12345678a' + if not contra.strip(): + msg = 'La contraseña es requerida' + print(msg) + return + + path_cer = Path(args.cer) + path_key = Path(args.key) + + if not path_cer.is_file(): + msg = 'El archivo CER es necesario' + print(msg) + return + + if not path_key.is_file(): + msg = 'El archivo KEY es necesario' + print(msg) + return + + cer = path_cer.read_bytes() + key = path_key.read_bytes() + cert = SATCertificate(cer, key, contra) + + if cert.error: + print(cert.error) + else: + print(cert) + return + + +def _process_command_line_arguments(): + parser = argparse.ArgumentParser(description='CFDI Certificados') + + help = 'Archivo CER' + parser.add_argument('-c', '--cer', help=help, default='') + help = 'Archivo KEY' + parser.add_argument('-k', '--key', help=help, default='') + + args = parser.parse_args() + return args + + +if __name__ == '__main__': + args = _process_command_line_arguments() + main(args) diff --git a/source/app/controllers/comercio/__init__.py b/source/app/controllers/comercio/__init__.py deleted file mode 100644 index 195aadd..0000000 --- a/source/app/controllers/comercio/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python3 - -from .comercio import PACComercioDigital diff --git a/source/app/controllers/comercio/comercio.py b/source/app/controllers/comercio/comercio.py deleted file mode 100644 index 8836156..0000000 --- a/source/app/controllers/comercio/comercio.py +++ /dev/null @@ -1,342 +0,0 @@ -#!/usr/bin/env python -# ~ -# ~ PAC -# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net -# ~ -# ~ This program is free software: you can redistribute it and/or modify -# ~ it under the terms of the GNU General Public License as published by -# ~ the Free Software Foundation, either version 3 of the License, or -# ~ (at your option) any later version. -# ~ -# ~ This program is distributed in the hope that it will be useful, -# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of -# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# ~ GNU General Public License for more details. -# ~ -# ~ You should have received a copy of the GNU General Public License -# ~ along with this program. If not, see . - - -import logging - -import lxml.etree as ET -import requests -from requests.exceptions import ConnectionError - - -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__) -logging.getLogger('requests').setLevel(logging.ERROR) - - -try: - from .conf import DEBUG, AUTH -except ImportError: - DEBUG = False - log.debug('Need make conf.py') - - -TIMEOUT = 10 - - -class PACComercioDigital(object): - ws = 'https://{}.comercio-digital.mx/{}' - api = 'https://app2.comercio-digital.mx/{}' - URL = { - 'timbra': ws.format('ws', 'timbre/timbrarV5.aspx'), - 'cancel': ws.format('cancela', 'cancela3/cancelarUuid'), - 'cancelxml': ws.format('cancela', 'cancela3/cancelarXml'), - 'client': api.format('x3/altaEmpresa'), - 'saldo': api.format('x3/saldo'), - 'timbres': api.format('x3/altaTimbres'), - } - CODES = { - '000': '000 Exitoso', - '004': '004 RFC {} ya esta dado de alta con Estatus=A', - '704': '704 Usuario Invalido', - } - NS_CFDI = { - 'cfdi': 'http://www.sat.gob.mx/cfd/3', - 'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital', - } - - if DEBUG: - ws = 'https://pruebas.comercio-digital.mx/{}' - URL = { - 'timbra': ws.format('timbre/timbrarV5.aspx'), - 'cancel': ws.format('cancela3/cancelarUuid'), - 'cancelxml': ws.format('cancela3/cancelarXml'), - 'client': api.format('x3/altaEmpresa'), - 'saldo': api.format('x3/saldo'), - 'timbres': api.format('x3/altaTimbres'), - } - - def __init__(self): - self.error = '' - self.cfdi_uuid = '' - self.date_stamped = '' - - def _error(self, msg): - self.error = str(msg) - log.error(msg) - return - - def _post(self, url, data, headers={}): - result = None - headers['host'] = url.split('/')[2] - headers['Content-type'] = 'text/plain' - headers['Connection'] = 'Keep-Alive' - - try: - result = requests.post(url, data=data, headers=headers, timeout=TIMEOUT) - except ConnectionError as e: - self._error(e) - - return result - - def _validate_cfdi(self, xml): - """ - Comercio Digital solo soporta la declaración con doble comilla - """ - tree = ET.fromstring(xml.encode()) - xml = ET.tostring(tree, - pretty_print=True, doctype='') - return xml - - def stamp(self, cfdi, auth={}): - if DEBUG or not auth: - auth = AUTH - - url = self.URL['timbra'] - headers = { - 'usrws': auth['user'], - 'pwdws': auth['pass'], - 'tipo': 'XML', - } - cfdi = self._validate_cfdi(cfdi) - result = self._post(url, cfdi, headers) - - if result is None: - return '' - - if result.status_code != 200: - return '' - - if 'errmsg' in result.headers: - self._error(result.headers['errmsg']) - return '' - - xml = result.content - tree = ET.fromstring(xml) - self.cfdi_uuid = tree.xpath( - 'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)', - namespaces=self.NS_CFDI) - self.date_stamped = tree.xpath( - 'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@FechaTimbrado)', - namespaces=self.NS_CFDI) - - return xml.decode() - - def _get_data_cancel(self, cfdi, info, auth): - NS_CFDI = { - 'cfdi': 'http://www.sat.gob.mx/cfd/3', - 'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital', - } - tree = ET.fromstring(cfdi) - tipo = tree.xpath( - 'string(//cfdi:Comprobante/@TipoDeComprobante)', - namespaces=NS_CFDI) - total = tree.xpath( - 'string(//cfdi:Comprobante/@Total)', - namespaces=NS_CFDI) - rfc_emisor = tree.xpath( - 'string(//cfdi:Comprobante/cfdi:Emisor/@Rfc)', - namespaces=NS_CFDI) - rfc_receptor = tree.xpath( - 'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)', - namespaces=NS_CFDI) - uid = tree.xpath( - 'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)', - namespaces=NS_CFDI) - data = ( - f"USER={auth['user']}", - f"PWDW={auth['pass']}", - f"RFCE={rfc_emisor}", - f"UUID={uid}", - f"PWDK={info['pass']}", - f"KEYF={info['key']}", - f"CERT={info['cer']}", - f"TIPO={info['tipo']}", - f"ACUS=SI", - f"RFCR={rfc_receptor}", - f"TIPOC={tipo}", - f"TOTAL={total}", - ) - return '\n'.join(data) - - def cancel(self, cfdi, info, auth={}): - if not auth: - auth = AUTH - url = self.URL['cancel'] - data = self._get_data_cancel(cfdi, info, auth) - - result = self._post(url, data) - - if result is None: - return '' - - if result.status_code != 200: - return '' - - if result.headers['codigo'] != '000': - self._error(result.headers['errmsg']) - return '' - - return result.content - - def _get_headers_cancel_xml(self, cfdi, info, auth): - NS_CFDI = { - 'cfdi': 'http://www.sat.gob.mx/cfd/3', - 'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital', - } - tree = ET.fromstring(cfdi) - tipo = tree.xpath( - 'string(//cfdi:Comprobante/@TipoDeComprobante)', - namespaces=NS_CFDI) - total = tree.xpath( - 'string(//cfdi:Comprobante/@Total)', - namespaces=NS_CFDI) - rfc_receptor = tree.xpath( - 'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)', - namespaces=NS_CFDI) - - headers = { - 'usrws': auth['user'], - 'pwdws': auth['pass'], - 'rfcr': rfc_receptor, - 'total': total, - 'tipocfdi': tipo, - } - headers.update(info) - - return headers - - def cancel_xml(self, cfdi, xml, info, auth={}): - if not auth: - auth = AUTH - url = self.URL['cancelxml'] - headers = self._get_headers_cancel_xml(cfdi, info, auth) - result = self._post(url, xml, headers) - - if result is None: - return '' - - if result.status_code != 200: - return '' - - if result.headers['codigo'] != '000': - self._error(result.headers['errmsg']) - return '' - - return result.content - - def _get_data_client(self, auth, values): - data = [f"usr_ws={auth['user']}", f"pwd_ws={auth['pass']}"] - fields = ( - 'rfc_contribuyente', - 'nombre_contribuyente', - 'calle', - 'noExterior', - 'noInterior', - 'colonia', - 'localidad', - 'municipio', - 'estado', - 'pais', - 'cp', - 'contacto', - 'telefono', - 'email', - 'rep_nom', - 'rep_rfc', - 'email_fact', - 'pwd_asignado', - ) - data += [f"{k}={values[k]}" for k in fields] - - return '\n'.join(data) - - def client_add(self, data): - auth = AUTH - url = self.URL['client'] - data = self._get_data_client(auth, data) - - result = self._post(url, data) - - if result is None: - return False - - if result.status_code != 200: - self._error(f'Code: {result.status_code}') - return False - - if result.text != self.CODES['000']: - self._error(result.text) - return False - - return True - - def client_balance(self, data): - url = self.URL['saldo'] - host = url.split('/')[2] - headers = { - 'Content-type': 'text/plain', - 'Host': host, - 'Connection' : 'Keep-Alive', - } - try: - result = requests.get(url, params=data, headers=headers, timeout=TIMEOUT) - except ConnectionError as e: - self._error(e) - return '' - - if result.status_code != 200: - return '' - - if result.text == self.CODES['704']: - self._error(result.text) - return '' - - return result.text - - def client_add_timbres(self, data, auth={}): - if not auth: - auth = AUTH - url = self.URL['timbres'] - data = '\n'.join(( - f"usr_ws={auth['user']}", - f"pwd_ws={auth['pass']}", - f"rfc_recibir={data['rfc']}", - f"num_timbres={data['timbres']}" - )) - - result = self._post(url, data) - - if result is None: - return False - - if result.status_code != 200: - self._error(f'Code: {result.status_code}') - return False - - if result.text != self.CODES['000']: - self._error(result.text) - return False - - return True - diff --git a/source/app/controllers/comercio/conf.py.example b/source/app/controllers/comercio/conf.py.example deleted file mode 100644 index de81efb..0000000 --- a/source/app/controllers/comercio/conf.py.example +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# ~ -# ~ PAC -# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net -# ~ -# ~ This program is free software: you can redistribute it and/or modify -# ~ it under the terms of the GNU General Public License as published by -# ~ the Free Software Foundation, either version 3 of the License, or -# ~ (at your option) any later version. -# ~ -# ~ This program is distributed in the hope that it will be useful, -# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of -# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# ~ GNU General Public License for more details. -# ~ -# ~ You should have received a copy of the GNU General Public License -# ~ along with this program. If not, see . - - -# ~ Siempre consulta la documentación de Finkok -# ~ AUTH = Puedes usar credenciales genericas para timbrar, o exclusivas para -# ~ cada emisor -# ~ RESELLER = Algunos procesos como agregar emisores, solo pueden ser usadas -# ~ con una cuenta de reseller - - -DEBUG = False - - -AUTH = { - 'user': '', - 'pass': '', -} - - -if DEBUG: - AUTH = { - 'user': 'AAA010101AAA', - 'pass': 'PWD', - } diff --git a/source/app/controllers/configpac.py b/source/app/controllers/configpac.py index 814fefa..d4c70a0 100644 --- a/source/app/controllers/configpac.py +++ b/source/app/controllers/configpac.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -from .conf import DEBUG, ID_INTEGRADOR, FINKOK +from .conf import DEBUG, FINKOK DEBUG = DEBUG TIMEOUT = 10 @@ -11,34 +11,6 @@ TIMEOUT = 10 PAC = 'finkok' -def ecodex(debug): - NEW_SERVER = True - auth = {'ID': ID_INTEGRADOR} - if debug: - #~ No cambies este ID de pruebas - auth = {'ID': '2b3a8764-d586-4543-9b7e-82834443f219'} - - base_url = 'https://servicios.ecodex.com.mx:4043/Servicio{}.svc?wsdl' - if NEW_SERVER: - base_url = 'https://serviciosnominas.ecodex.com.mx:4043/Servicio{}.svc?wsdl' - base_api = 'https://api.ecodex.com.mx/{}' - if debug: - base_url = 'https://wsdev.ecodex.com.mx:2045/Servicio{}.svc?wsdl' - base_api = 'https://pruebasapi.ecodex.com.mx/{}' - url = { - 'seguridad': base_url.format('Seguridad'), - 'clients': base_url.format('Clientes'), - 'timbra': base_url.format('Timbrado'), - 'token': base_api.format('token?version=2'), - 'docs': base_api.format('api/documentos'), - 'hash': base_api.format('api/Documentos/{}'), - 'codes': { - 'HASH': 'DUPLICIDAD EN HASH', - } - } - return auth, url - - #~ IMPORTANTE: Si quieres hacer pruebas, con tu propio correo de usuario y #~ contraseña, ponte en contacto con Finkok para que te asignen tus datos de #~ acceso, consulta su documentación para ver las diferentes opciones de acceso. diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 3efcfc2..81441f1 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -632,3 +632,19 @@ class AppSociosCuentasBanco(object): req.context['result'] = self._db.partners_accounts_bank(values) resp.status = falcon.HTTP_200 + +class AppCert(object): + + def __init__(self, db): + self._db = db + + def on_get(self, req, resp): + values = req.params + req.context['result'] = self._db.cert_get(values) + resp.status = falcon.HTTP_200 + + def on_post(self, req, resp): + values = req.params + req.context['result'] = self._db.cert_post(values) + resp.status = falcon.HTTP_200 + diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 26e24d0..4465cfa 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -68,7 +68,7 @@ from settings import DEBUG, MV, log, template_lookup, COMPANIES, DB_SAT, \ PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \ PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES, DIR_FACTURAS -from settings import SEAFILE_SERVER, USAR_TOKEN, API, DECIMALES_TAX +from settings import USAR_TOKEN, API, DECIMALES_TAX from .configpac import AUTH diff --git a/source/app/controllers/utils.py b/source/app/controllers/utils.py index 0c498ab..6ba662b 100644 --- a/source/app/controllers/utils.py +++ b/source/app/controllers/utils.py @@ -48,9 +48,9 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from dateutil import parser -import seafileapi - from settings import DEBUG, DB_COMPANIES, PATHS + +from .cfdi_cert import SATCertificate from .pacs import PACComercioDigital from .pac import Finkok as PACFinkok # ~ from .finkok import PACFinkok @@ -492,29 +492,6 @@ def _backup_db(rfc, is_mv, url_seafile): shutil.copy(path, path_target) else: log.error('\tNo existe la carpeta compartida...') - - # ~ sql = 'select correo_timbrado, token_soporte from emisor;' - # ~ args = 'psql -U postgres -d {} -Atc "{}"'.format(data['name'], sql) - # ~ result = _call(args) - # ~ if not result: - # ~ log.error('\tSin datos para backup remoto') - # ~ return - - # ~ data = result.strip().split('|') - # ~ if not data[1]: - # ~ log.error('\tSin token de soporte') - # ~ return - - # ~ email = data[0] - # ~ uuid = data[1] - # ~ email = 'hola@elmau.net' - # ~ uuid = 'cc42c591-cf66-499a-ae70-c09df5646be9' - - # ~ log.debug(url_seafile, email, _get_pass(rfc)) - # ~ client = seafileapi.connect(url_seafile, email, _get_pass(rfc)) - # ~ repo = client.repos.get_repo(uuid) - # ~ print(repo) - return @@ -629,14 +606,30 @@ def xml_cancel(xml, auth, cert, name): return data, result -def get_client_balance(auth, name): +def get_client_balance(auth): if DEBUG: return '-d' - pac = PACS[name]() - auth = {'usr': auth['USER'], 'pwd': auth['PASS']} + pac = PACS[auth['pac']]() balance = pac.client_balance(auth) if pac.error: balance = '-e' return balance + + +def get_cert(args): + p1 = '/home/mau/Desktop/Pruebas_EKU9003173C9/file.cer' + cer = args['cer'] + # ~ cer = cer.encode() + # ~ cer = base64.b64decode(args['cer'].encode()) + with open(p1, 'w') as f: + f.write(cer) + # ~ cer = base64.b64decode(args['cer'].encode()) + print('TYPE', type(cer)) + # ~ print(cer) + # ~ key = base64.b64decode(args['key'].encode()) + # ~ cert = SATCertificate(cer, key, args['contra']) + return + + diff --git a/source/app/main.py b/source/app/main.py index c03b858..cb28630 100644 --- a/source/app/main.py +++ b/source/app/main.py @@ -17,7 +17,7 @@ from controllers.main import (AppEmpresas, AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco, AppMovimientosBanco, AppTickets, AppStudents, AppEmployees, AppNomina, AppInvoicePay, AppCfdiPay, AppSATBancos, AppSociosCuentasBanco, - AppSATFormaPago, AppSATLeyendaFiscales + AppSATFormaPago, AppSATLeyendaFiscales, AppCert ) @@ -62,6 +62,7 @@ api.add_route('/satbancos', AppSATBancos(db)) api.add_route('/satformapago', AppSATFormaPago(db)) api.add_route('/socioscb', AppSociosCuentasBanco(db)) api.add_route('/leyendasfiscales', AppSATLeyendaFiscales(db)) +api.add_route('/cert', AppCert(db)) session_options = { diff --git a/source/app/models/db.py b/source/app/models/db.py index 7d50926..2219a2d 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -471,6 +471,13 @@ class StorageEngine(object): def sat_leyendas_fiscales_delete(self, values): return main.SATLeyendasFiscales.remove(values) + # ~ v2 + def cert_get(self, values): + return main.Certificado.get_data(values) + + def cert_post(self, values): + return main.Certificado.post(values) + # Companies only in MV def _get_empresas(self, values): return main.companies_get() diff --git a/source/app/models/main.py b/source/app/models/main.py index 798dc7e..3fdcb54 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1165,19 +1165,35 @@ class Certificado(BaseModel): return self.serie @classmethod - def get_cert(cls, is_fiel=False): - return Certificado.get(Certificado.es_fiel==is_fiel) - - @classmethod - def get_data(cls): - obj = cls.get_(cls) - row = { + def _get_cert(cls, args): + obj = Certificado.get(Certificado.es_fiel==False) + data = { 'cert_rfc': obj.rfc, 'cert_serie': obj.serie, 'cert_desde': obj.desde, 'cert_hasta': obj.hasta, } - return row + return data + + @classmethod + def get_data(cls, values): + opt = values['opt'] + return getattr(cls, f'_get_{opt}')(values) + + @classmethod + def _validate(cls, args): + cert = utils.get_cert(args) + print(cert) + return {'ok': False, 'msg': 'error', 'data': {}} + + @classmethod + def post(cls, values): + opt = values['opt'] + return getattr(cls, f'_{opt}')(values) + + # ~ @classmethod + # ~ def get_cert(cls, is_fiel=False): + # ~ return Certificado.get(Certificado.es_fiel==is_fiel) def get_(cls): return Certificado.select()[0] diff --git a/source/app/seafileapi/__init__.py b/source/app/seafileapi/__init__.py deleted file mode 100644 index d6c3b8d..0000000 --- a/source/app/seafileapi/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from seafileapi.client import SeafileApiClient - -def connect(server, username, password): - client = SeafileApiClient(server, username, password) - return client diff --git a/source/app/seafileapi/admin.py b/source/app/seafileapi/admin.py deleted file mode 100644 index 08fc2c4..0000000 --- a/source/app/seafileapi/admin.py +++ /dev/null @@ -1,7 +0,0 @@ - -class SeafileAdmin(object): - def lists_users(self, maxcount=100): - pass - - def list_user_repos(self, username): - pass diff --git a/source/app/seafileapi/client.py b/source/app/seafileapi/client.py deleted file mode 100644 index 52a6ea6..0000000 --- a/source/app/seafileapi/client.py +++ /dev/null @@ -1,77 +0,0 @@ -import requests -from seafileapi.utils import urljoin -from seafileapi.exceptions import ClientHttpError -from seafileapi.repos import Repos - -class SeafileApiClient(object): - """Wraps seafile web api""" - def __init__(self, server, username=None, password=None, token=None): - """Wraps various basic operations to interact with seahub http api. - """ - self.server = server - self.username = username - self.password = password - self._token = token - - self.repos = Repos(self) - self.groups = Groups(self) - - if token is None: - self._get_token() - - def _get_token(self): - data = { - 'username': self.username, - 'password': self.password, - } - url = urljoin(self.server, '/api2/auth-token/') - res = requests.post(url, data=data) - if res.status_code != 200: - raise ClientHttpError(res.status_code, res.content) - token = res.json()['token'] - assert len(token) == 40, 'The length of seahub api auth token should be 40' - self._token = token - - def __str__(self): - return 'SeafileApiClient[server=%s, user=%s]' % (self.server, self.username) - - __repr__ = __str__ - - def get(self, *args, **kwargs): - return self._send_request('GET', *args, **kwargs) - - def post(self, *args, **kwargs): - return self._send_request('POST', *args, **kwargs) - - def put(self, *args, **kwargs): - return self._send_request('PUT', *args, **kwargs) - - def delete(self, *args, **kwargs): - return self._send_request('delete', *args, **kwargs) - - def _send_request(self, method, url, *args, **kwargs): - if not url.startswith('http'): - url = urljoin(self.server, url) - - headers = kwargs.get('headers', {}) - headers.setdefault('Authorization', 'Token ' + self._token) - kwargs['headers'] = headers - - expected = kwargs.pop('expected', 200) - if not hasattr(expected, '__iter__'): - expected = (expected, ) - resp = requests.request(method, url, *args, **kwargs) - if resp.status_code not in expected: - msg = 'Expected %s, but get %s' % \ - (' or '.join(map(str, expected)), resp.status_code) - raise ClientHttpError(resp.status_code, msg) - - return resp - - -class Groups(object): - def __init__(self, client): - pass - - def create_group(self, name): - pass diff --git a/source/app/seafileapi/exceptions.py b/source/app/seafileapi/exceptions.py deleted file mode 100644 index b11498d..0000000 --- a/source/app/seafileapi/exceptions.py +++ /dev/null @@ -1,25 +0,0 @@ - -class ClientHttpError(Exception): - """This exception is raised if the returned http response is not as - expected""" - def __init__(self, code, message): - super(ClientHttpError, self).__init__() - self.code = code - self.message = message - - def __str__(self): - return 'ClientHttpError[%s: %s]' % (self.code, self.message) - -class OperationError(Exception): - """Expcetion to raise when an opeartion is failed""" - pass - - -class DoesNotExist(Exception): - """Raised when not matching resource can be found.""" - def __init__(self, msg): - super(DoesNotExist, self).__init__() - self.msg = msg - - def __str__(self): - return 'DoesNotExist: %s' % self.msg diff --git a/source/app/seafileapi/files.py b/source/app/seafileapi/files.py deleted file mode 100644 index ed01e64..0000000 --- a/source/app/seafileapi/files.py +++ /dev/null @@ -1,250 +0,0 @@ -import io -import os -import posixpath -import re -from seafileapi.utils import querystr - -ZERO_OBJ_ID = '0000000000000000000000000000000000000000' - -class _SeafDirentBase(object): - """Base class for :class:`SeafFile` and :class:`SeafDir`. - - It provides implementation of their common operations. - """ - isdir = None - - def __init__(self, repo, path, object_id, size=0): - """ - :param:`path` the full path of this entry within its repo, like - "/documents/example.md" - - :param:`size` The size of a file. It should be zero for a dir. - """ - self.client = repo.client - self.repo = repo - self.path = path - self.id = object_id - self.size = size - - @property - def name(self): - return posixpath.basename(self.path) - - def list_revisions(self): - pass - - def delete(self): - suffix = 'dir' if self.isdir else 'file' - url = '/api2/repos/%s/%s/' % (self.repo.id, suffix) + querystr(p=self.path) - resp = self.client.delete(url) - return resp - - def rename(self, newname): - """Change file/folder name to newname - """ - suffix = 'dir' if self.isdir else 'file' - url = '/api2/repos/%s/%s/' % (self.repo.id, suffix) + querystr(p=self.path, reloaddir='true') - postdata = {'operation': 'rename', 'newname': newname} - resp = self.client.post(url, data=postdata) - succeeded = resp.status_code == 200 - if succeeded: - if self.isdir: - new_dirent = self.repo.get_dir(os.path.join(os.path.dirname(self.path), newname)) - else: - new_dirent = self.repo.get_file(os.path.join(os.path.dirname(self.path), newname)) - for key in list(self.__dict__.keys()): - self.__dict__[key] = new_dirent.__dict__[key] - return succeeded - - def _copy_move_task(self, operation, dirent_type, dst_dir, dst_repo_id=None): - url = '/api/v2.1/copy-move-task/' - src_repo_id = self.repo.id - src_parent_dir = os.path.dirname(self.path) - src_dirent_name = os.path.basename(self.path) - dst_repo_id = dst_repo_id - dst_parent_dir = dst_dir - operation = operation - dirent_type = dirent_type - postdata = {'src_repo_id': src_repo_id, 'src_parent_dir': src_parent_dir, - 'src_dirent_name': src_dirent_name, 'dst_repo_id': dst_repo_id, - 'dst_parent_dir': dst_parent_dir, 'operation': operation, - 'dirent_type': dirent_type} - return self.client.post(url, data=postdata) - - def copyTo(self, dst_dir, dst_repo_id=None): - """Copy file/folder to other directory (also to a different repo) - """ - if dst_repo_id is None: - dst_repo_id = self.repo.id - - dirent_type = 'dir' if self.isdir else 'file' - resp = self._copy_move_task('copy', dirent_type, dst_dir, dst_repo_id) - return resp.status_code == 200 - - def moveTo(self, dst_dir, dst_repo_id=None): - """Move file/folder to other directory (also to a different repo) - """ - if dst_repo_id is None: - dst_repo_id = self.repo.id - - dirent_type = 'dir' if self.isdir else 'file' - resp = self._copy_move_task('move', dirent_type, dst_dir, dst_repo_id) - succeeded = resp.status_code == 200 - if succeeded: - new_repo = self.client.repos.get_repo(dst_repo_id) - dst_path = os.path.join(dst_dir, os.path.basename(self.path)) - if self.isdir: - new_dirent = new_repo.get_dir(dst_path) - else: - new_dirent = new_repo.get_file(dst_path) - for key in list(self.__dict__.keys()): - self.__dict__[key] = new_dirent.__dict__[key] - return succeeded - - def get_share_link(self): - pass - -class SeafDir(_SeafDirentBase): - isdir = True - - def __init__(self, *args, **kwargs): - super(SeafDir, self).__init__(*args, **kwargs) - self.entries = None - self.entries = kwargs.pop('entries', None) - - def ls(self, force_refresh=False): - """List the entries in this dir. - - Return a list of objects of class :class:`SeafFile` or :class:`SeafDir`. - """ - if self.entries is None or force_refresh: - self.load_entries() - - return self.entries - - def share_to_user(self, email, permission): - url = '/api2/repos/%s/dir/shared_items/' % self.repo.id + querystr(p=self.path) - putdata = { - 'share_type': 'user', - 'username': email, - 'permission': permission - } - resp = self.client.put(url, data=putdata) - return resp.status_code == 200 - - def create_empty_file(self, name): - """Create a new empty file in this dir. - Return a :class:`SeafFile` object of the newly created file. - """ - # TODO: file name validation - path = posixpath.join(self.path, name) - url = '/api2/repos/%s/file/' % self.repo.id + querystr(p=path, reloaddir='true') - postdata = {'operation': 'create'} - resp = self.client.post(url, data=postdata) - self.id = resp.headers['oid'] - self.load_entries(resp.json()) - return SeafFile(self.repo, path, ZERO_OBJ_ID, 0) - - def mkdir(self, name): - """Create a new sub folder right under this dir. - - Return a :class:`SeafDir` object of the newly created sub folder. - """ - path = posixpath.join(self.path, name) - url = '/api2/repos/%s/dir/' % self.repo.id + querystr(p=path, reloaddir='true') - postdata = {'operation': 'mkdir'} - resp = self.client.post(url, data=postdata) - self.id = resp.headers['oid'] - self.load_entries(resp.json()) - return SeafDir(self.repo, path, ZERO_OBJ_ID) - - def upload(self, fileobj, filename): - """Upload a file to this folder. - - :param:fileobj :class:`File` like object - :param:filename The name of the file - - Return a :class:`SeafFile` object of the newly uploaded file. - """ - if isinstance(fileobj, str): - fileobj = io.BytesIO(fileobj) - upload_url = self._get_upload_link() - files = { - 'file': (filename, fileobj), - 'parent_dir': self.path, - } - self.client.post(upload_url, files=files) - return self.repo.get_file(posixpath.join(self.path, filename)) - - def upload_local_file(self, filepath, name=None): - """Upload a file to this folder. - - :param:filepath The path to the local file - :param:name The name of this new file. If None, the name of the local file would be used. - - Return a :class:`SeafFile` object of the newly uploaded file. - """ - name = name or os.path.basename(filepath) - with open(filepath, 'r') as fp: - return self.upload(fp, name) - - def _get_upload_link(self): - url = '/api2/repos/%s/upload-link/' % self.repo.id - resp = self.client.get(url) - return re.match(r'"(.*)"', resp.text).group(1) - - def get_uploadable_sharelink(self): - """Generate a uploadable shared link to this dir. - - Return the url of this link. - """ - pass - - def load_entries(self, dirents_json=None): - if dirents_json is None: - url = '/api2/repos/%s/dir/' % self.repo.id + querystr(p=self.path) - dirents_json = self.client.get(url).json() - - self.entries = [self._load_dirent(entry_json) for entry_json in dirents_json] - - def _load_dirent(self, dirent_json): - path = posixpath.join(self.path, dirent_json['name']) - if dirent_json['type'] == 'file': - return SeafFile(self.repo, path, dirent_json['id'], dirent_json['size']) - else: - return SeafDir(self.repo, path, dirent_json['id'], 0) - - @property - def num_entries(self): - if self.entries is None: - self.load_entries() - return len(self.entries) if self.entries is not None else 0 - - def __str__(self): - return 'SeafDir[repo=%s,path=%s,entries=%s]' % \ - (self.repo.id[:6], self.path, self.num_entries) - - __repr__ = __str__ - -class SeafFile(_SeafDirentBase): - isdir = False - - def update(self, fileobj): - """Update the content of this file""" - pass - - def __str__(self): - return 'SeafFile[repo=%s,path=%s,size=%s]' % \ - (self.repo.id[:6], self.path, self.size) - - def _get_download_link(self): - url = '/api2/repos/%s/file/' % self.repo.id + querystr(p=self.path) - resp = self.client.get(url) - return re.match(r'"(.*)"', resp.text).group(1) - - def get_content(self): - """Get the content of the file""" - url = self._get_download_link() - return self.client.get(url).content - - __repr__ = __str__ diff --git a/source/app/seafileapi/group.py b/source/app/seafileapi/group.py deleted file mode 100644 index 731d7ef..0000000 --- a/source/app/seafileapi/group.py +++ /dev/null @@ -1,22 +0,0 @@ - - -class Group(object): - def __init__(self, client, group_id, group_name): - self.client = client - self.group_id = group_id - self.group_name = group_name - - def list_memebers(self): - pass - - def delete(self): - pass - - def add_member(self, username): - pass - - def remove_member(self, username): - pass - - def list_group_repos(self): - pass diff --git a/source/app/seafileapi/repo.py b/source/app/seafileapi/repo.py deleted file mode 100644 index 01811a2..0000000 --- a/source/app/seafileapi/repo.py +++ /dev/null @@ -1,99 +0,0 @@ -from urllib.parse import urlencode -from seafileapi.files import SeafDir, SeafFile -from seafileapi.utils import raise_does_not_exist - -class Repo(object): - """ - A seafile library - """ - def __init__(self, client, repo_id, repo_name, - encrypted, owner, perm): - self.client = client - self.id = repo_id - self.name = repo_name - self.encrypted = encrypted - self.owner = owner - self.perm = perm - - @classmethod - def from_json(cls, client, repo_json): - - repo_id = repo_json['id'] - repo_name = repo_json['name'] - encrypted = repo_json['encrypted'] - perm = repo_json['permission'] - owner = repo_json['owner'] - - return cls(client, repo_id, repo_name, encrypted, owner, perm) - - def is_readonly(self): - return 'w' not in self.perm - - @raise_does_not_exist('The requested file does not exist') - def get_file(self, path): - """Get the file object located in `path` in this repo. - - Return a :class:`SeafFile` object - """ - assert path.startswith('/') - url = '/api2/repos/%s/file/detail/' % self.id - query = '?' + urlencode(dict(p=path)) - file_json = self.client.get(url + query).json() - - return SeafFile(self, path, file_json['id'], file_json['size']) - - @raise_does_not_exist('The requested dir does not exist') - def get_dir(self, path): - """Get the dir object located in `path` in this repo. - - Return a :class:`SeafDir` object - """ - assert path.startswith('/') - url = '/api2/repos/%s/dir/' % self.id - query = '?' + urlencode(dict(p=path)) - resp = self.client.get(url + query) - dir_id = resp.headers['oid'] - dir_json = resp.json() - dir = SeafDir(self, path, dir_id) - dir.load_entries(dir_json) - return dir - - def delete(self): - """Remove this repo. Only the repo owner can do this""" - self.client.delete('/api2/repos/' + self.id) - - def list_history(self): - """List the history of this repo - - Returns a list of :class:`RepoRevision` object. - """ - pass - - ## Operations only the repo owner can do: - - def update(self, name=None): - """Update the name of this repo. Only the repo owner can do - this. - """ - pass - - def get_settings(self): - """Get the settings of this repo. Returns a dict containing the following - keys: - - `history_limit`: How many days of repo history to keep. - """ - pass - - def restore(self, commit_id): - pass - -class RepoRevision(object): - def __init__(self, client, repo, commit_id): - self.client = client - self.repo = repo - self.commit_id = commit_id - - def restore(self): - """Restore the repo to this revision""" - self.repo.revert(self.commit_id) diff --git a/source/app/seafileapi/repos.py b/source/app/seafileapi/repos.py deleted file mode 100644 index 70a8fa7..0000000 --- a/source/app/seafileapi/repos.py +++ /dev/null @@ -1,26 +0,0 @@ -from seafileapi.repo import Repo -from seafileapi.utils import raise_does_not_exist - -class Repos(object): - def __init__(self, client): - self.client = client - - def create_repo(self, name, password=None): - data = {'name': name} - if password: - data['passwd'] = password - repo_json = self.client.post('/api2/repos/', data=data).json() - return self.get_repo(repo_json['repo_id']) - - @raise_does_not_exist('The requested library does not exist') - def get_repo(self, repo_id): - """Get the repo which has the id `repo_id`. - - Raises :exc:`DoesNotExist` if no such repo exists. - """ - repo_json = self.client.get('/api2/repos/' + repo_id).json() - return Repo.from_json(self.client, repo_json) - - def list_repos(self): - repos_json = self.client.get('/api2/repos/').json() - return [Repo.from_json(self.client, j) for j in repos_json] diff --git a/source/app/seafileapi/utils.py b/source/app/seafileapi/utils.py deleted file mode 100644 index 7903414..0000000 --- a/source/app/seafileapi/utils.py +++ /dev/null @@ -1,57 +0,0 @@ -import string -import random -from functools import wraps -from urllib.parse import urlencode -from seafileapi.exceptions import ClientHttpError, DoesNotExist - -def randstring(length=0): - if length == 0: - length = random.randint(1, 30) - return ''.join(random.choice(string.lowercase) for i in range(length)) - -def urljoin(base, *args): - url = base - if url[-1] != '/': - url += '/' - for arg in args: - arg = arg.strip('/') - url += arg + '/' - if '?' in url: - url = url[:-1] - return url - -def raise_does_not_exist(msg): - """Decorator to turn a function that get a http 404 response to a - :exc:`DoesNotExist` exception.""" - def decorator(func): - @wraps(func) - def wrapped(*args, **kwargs): - try: - return func(*args, **kwargs) - except ClientHttpError as e: - if e.code == 404: - raise DoesNotExist(msg) - else: - raise - return wrapped - return decorator - -def to_utf8(obj): - if isinstance(obj, str): - return obj.encode('utf-8') - return obj - -def querystr(**kwargs): - return '?' + urlencode(kwargs) - -def utf8lize(obj): - if isinstance(obj, dict): - return {k: to_utf8(v) for k, v in obj.items()} - - if isinstance(obj, list): - return [to_utf8(x) for x in ob] - - if instance(obj, str): - return obj.encode('utf-8') - - return obj diff --git a/source/app/settings.py b/source/app/settings.py index 72ced22..ed389e4 100644 --- a/source/app/settings.py +++ b/source/app/settings.py @@ -30,11 +30,6 @@ try: except ImportError: DEFAULT_PASSWORD = 'salgueiro3.3' -try: - from conf import SEAFILE_SERVER -except ImportError: - SEAFILE_SERVER = {} - try: from conf import TITLE_APP except ImportError: diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 956faa4..80a3b82 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -19,6 +19,9 @@ var msg = '' var tb_options = null var tb_sat = null +var file_cer = null +var file_key = null + var controllers = { init: function(){ @@ -32,7 +35,9 @@ var controllers = { $$('chk_escuela').attachEvent('onChange', chk_escuela_change) $$('chk_ong').attachEvent('onChange', chk_ong_change) $$('cmd_subir_certificado').attachEvent('onItemClick', cmd_subir_certificado_click) - $$('up_cert').attachEvent('onUploadComplete', up_cert_upload_complete) + //~ $$('up_cert').attachEvent('onUploadComplete', up_cert_upload_complete) + //~ $$('up_cert').attachEvent('onAfterFileAdd', up_cert_after_file_add) + $$('up_cert').attachEvent('onBeforeFileAdd', up_cert_before_file_add) $$('cmd_agregar_serie').attachEvent('onItemClick', cmd_agregar_serie_click) $$('grid_folios').attachEvent('onItemClick', grid_folios_click) $$('chk_folio_custom').attachEvent('onItemClick', chk_config_item_click) @@ -280,7 +285,7 @@ function get_emisor(){ function get_certificado(){ var form = $$('form_cert') - webix.ajax().get("/values/cert", {}, { + webix.ajax().get("/cert", {'opt': 'cert'}, { error: function(text, data, xhr) { msg = 'Error al consultar' msg_error(msg) @@ -602,105 +607,6 @@ function chk_ong_change(new_value, old_value){ } -function cmd_subir_certificado_click(){ - var form = $$('form_upload') - - if (!form.validate()){ - msg = 'Valores inválidos' - msg_error(msg) - return - } - - var values = form.getValues() - - if(!values.contra.trim()){ - msg = 'La contraseña no puede estar vacía' - msg_error(msg) - return - } - - if($$('lst_cert').count() < 2){ - msg = 'Selecciona al menos dos archivos: CER y KEY del certificado.' - msg_error(msg) - return - } - - if($$('lst_cert').count() > 2){ - msg = 'Selecciona solo dos archivos: CER y KEY del certificado.' - msg_error(msg) - return - } - - var fo1 = $$('up_cert').files.getItem($$('up_cert').files.getFirstId()) - var fo2 = $$('up_cert').files.getItem($$('up_cert').files.getLastId()) - - var ext = ['key', 'cer'] - if(ext.indexOf(fo1.type.toLowerCase()) == -1 || ext.indexOf(fo2.type.toLowerCase()) == -1){ - msg = 'Archivos inválidos, se requiere un archivo CER y un KEY.' - msg_error(msg) - return - } - - if(fo1.type == fo2.type && fo1.size == fo2.size){ - msg = 'Selecciona archivos diferentes: un archivo CER y un KEY.' - msg_error(msg) - return - } - - var serie = $$('form_cert').getValues()['cert_serie'] - - if(serie){ - msg = 'Ya existe un certificado guardado

¿Deseas reemplazarlo?' - webix.confirm({ - title: 'Certificado Existente', - ok: 'Si', - cancel: 'No', - type: 'confirm-error', - text: msg, - callback:function(result){ - if(result){ - $$('up_cert').send() - } - } - }) - }else{ - $$('up_cert').send() - } -} - - -function up_cert_upload_complete(response){ - if(response.status != 'server'){ - msg = 'Ocurrio un error al subir los archivos' - msg_error(msg) - return - } - - msg = 'Archivos subidos correctamente. Esperando validación' - msg_ok(msg) - - var values = $$('form_upload').getValues() - $$('form_upload').setValues({}) - $$('up_cert').files.data.clearAll() - - webix.ajax().post('/values/cert', values, { - error:function(text, data, XmlHttpRequest){ - msg = 'Ocurrio un error, consulta a soporte técnico' - msg_error(msg) - }, - success:function(text, data, XmlHttpRequest){ - var values = data.json() - if(values.ok){ - $$('form_cert').setValues(values.data) - msg_ok(values.msg) - }else{ - msg_error(values.msg) - } - } - }) -} - - function cmd_agregar_serie_click(){ var form = $$('form_folios') var grid = $$('grid_folios') @@ -2726,3 +2632,175 @@ function cmd_save_pac_click(){ } }) } + + +function cmd_subir_certificado_click(){ + var form = $$('form_upload') + + if (!form.validate()){ + msg = 'Valores inválidos' + msg_error(msg) + return + } + + var values = form.getValues() + + if(!values.contra.trim()){ + msg = 'La contraseña no puede estar vacía' + msg_error(msg) + return + } + + //~ if($$('lst_cert').count() < 2){ + //~ msg = 'Selecciona al menos dos archivos: CER y KEY del certificado.' + //~ msg_error(msg) + //~ return + //~ } + + //~ if($$('lst_cert').count() > 2){ + //~ msg = 'Selecciona solo dos archivos: CER y KEY del certificado.' + //~ msg_error(msg) + //~ return + //~ } + + //~ var fo1 = $$('up_cert').files.getItem($$('up_cert').files.getFirstId()) + //~ var fo2 = $$('up_cert').files.getItem($$('up_cert').files.getLastId()) + + //~ var ext = ['key', 'cer'] + //~ if(ext.indexOf(fo1.type.toLowerCase()) == -1 || ext.indexOf(fo2.type.toLowerCase()) == -1){ + //~ msg = 'Archivos inválidos, se requiere un archivo CER y un KEY.' + //~ msg_error(msg) + //~ return + //~ } + + //~ if(fo1.type == fo2.type && fo1.size == fo2.size){ + //~ msg = 'Selecciona archivos diferentes: un archivo CER y un KEY.' + //~ msg_error(msg) + //~ return + //~ } + + var serie = $$('form_cert').getValues()['cert_serie'] + + if(serie){ + msg = 'Ya existe un certificado guardado

¿Deseas reemplazarlo?' + webix.confirm({ + title: 'Certificado Existente', + ok: 'Si', + cancel: 'No', + type: 'confirm-error', + text: msg, + callback:function(result){ + if(!result){ + return + } + } + }) + } + + //~ if (fo1.type.toLowerCase()=='cer'){ + //~ values['cer'] = fo1.file + //~ values['key'] = fo2.file + //~ } else { + //~ values['key'] = fo1.file + //~ values['cer'] = fo2.file + //~ } + $$('form_upload').setValues({}) + $$('up_cert').files.data.clearAll() + + values['cer'] = file_cer + values['key'] = file_key + validate_cert(values) +} + + +function up_cert_before_file_add(file){ + if (file.type.toLowerCase() != 'cer' && file.type.toLowerCase() != 'key'){ + msg_error('Selecciona un archivo CER o KEY') + return false + } + + var count = $$('lst_cert').count() + if (count > 1){ + msg = 'Selecciona solo dos archivos: CER y KEY del certificado.' + msg_error(msg) + return false + } + + if (count > 0){ + var f = $$('up_cert').files.getItem($$('up_cert').files.getFirstId()) + if (f.type.toLowerCase() == file.type.toLowerCase()){ + msg = 'Selecciona archivos diferentes: un archivo CER y un KEY.' + msg_error(msg) + return false + } + } + + var reader = new FileReader(); + if (file.type.toLowerCase() == 'cer'){ + reader.addEventListener('load', (event) => { + file_cer = event.target.result; + }); + reader.readAsBinaryString(file.file); + } else { + reader.addEventListener('load', (event) => { + file_key = event.target.result; + }); + reader.readAsBinaryString(file.file); + } +} + + +function validate_cert(values){ + msg = 'Archivos recibidos correctamente. Esperando validación' + msg_ok(msg) + + values['opt'] = 'validate' + webix.ajax().post('/cert', values, { + error:function(text, data, XmlHttpRequest){ + msg = 'Ocurrio un error, consulta a soporte técnico' + msg_error(msg) + }, + success:function(text, data, XmlHttpRequest){ + var values = data.json() + if(values.ok){ + $$('form_cert').setValues(values.data) + msg_ok(values.msg) + }else{ + msg_error(values.msg) + } + } + }) +} + + +//~ function up_cert_upload_complete(response){ + //~ if(response.status != 'server'){ + //~ msg = 'Ocurrio un error al subir los archivos' + //~ msg_error(msg) + //~ return + //~ } + + //~ msg = 'Archivos subidos correctamente. Esperando validación' + //~ msg_ok(msg) + + //~ var values = $$('form_upload').getValues() + //~ $$('form_upload').setValues({}) + //~ $$('up_cert').files.data.clearAll() + //~ values['opt'] = 'validate' + + //~ webix.ajax().post('/cert', values, { + //~ error:function(text, data, XmlHttpRequest){ + //~ msg = 'Ocurrio un error, consulta a soporte técnico' + //~ msg_error(msg) + //~ }, + //~ success:function(text, data, XmlHttpRequest){ + //~ var values = data.json() + //~ if(values.ok){ + //~ $$('form_cert').setValues(values.data) + //~ msg_ok(values.msg) + //~ }else{ + //~ msg_error(values.msg) + //~ } + //~ } + //~ }) +//~ } diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index 2805483..0e02fbd 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -278,18 +278,21 @@ var col_fiel = {rows: [ ]} + //~ {view: 'uploader', id: 'up_cert', autosend: false, link: 'lst_cert', + //~ value: 'Seleccionar certificado', upload: '/values/files'}, {}]}, + var emisor_certificado = [ {cols: [col_sello, col_fiel]}, {template: 'Cargar Certificado', type: 'section'}, {view: 'form', id: 'form_upload', rows: [ {cols: [{}, {view: 'uploader', id: 'up_cert', autosend: false, link: 'lst_cert', - value: 'Seleccionar certificado', upload: '/values/files'}, {}]}, + value: 'Seleccionar certificado'}, {}]}, {cols: [{}, {view: 'list', id: 'lst_cert', name: 'certificado', type: 'uploader', autoheight:true, borderless: true}, {}]}, {cols: [{}, - {view: 'text', id: 'txt_contra', name: 'contra', + {view: 'text', id: 'txt_contra', name: 'contra', value: '12345678a', label: 'Contraseña KEY', labelPosition: 'top', labelAlign: 'center', type: 'password', required: true}, {}]}, {cols: [{}, {view: 'button', id: 'cmd_subir_certificado',