forked from elmau/empresa-libre
Refactory class certificates
This commit is contained in:
parent
56e52782f4
commit
85c5a37798
|
@ -13,6 +13,7 @@ pypng
|
||||||
reportlab
|
reportlab
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
cryptography
|
cryptography
|
||||||
|
xmlsec
|
||||||
|
|
||||||
# escpos
|
# escpos
|
||||||
# pyusb
|
# pyusb
|
||||||
|
|
|
@ -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)
|
|
@ -1,3 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from .comercio import PACComercioDigital
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
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='<?xml version="1.0" encoding="utf-8"?>')
|
|
||||||
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
|
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
# ~ 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',
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
|
||||||
from .conf import DEBUG, ID_INTEGRADOR, FINKOK
|
from .conf import DEBUG, FINKOK
|
||||||
|
|
||||||
DEBUG = DEBUG
|
DEBUG = DEBUG
|
||||||
TIMEOUT = 10
|
TIMEOUT = 10
|
||||||
|
@ -11,34 +11,6 @@ TIMEOUT = 10
|
||||||
PAC = 'finkok'
|
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
|
#~ 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
|
#~ 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.
|
#~ acceso, consulta su documentación para ver las diferentes opciones de acceso.
|
||||||
|
|
|
@ -632,3 +632,19 @@ class AppSociosCuentasBanco(object):
|
||||||
req.context['result'] = self._db.partners_accounts_bank(values)
|
req.context['result'] = self._db.partners_accounts_bank(values)
|
||||||
resp.status = falcon.HTTP_200
|
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
|
||||||
|
|
||||||
|
|
|
@ -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_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \
|
||||||
PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES, DIR_FACTURAS
|
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
|
from .configpac import AUTH
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -48,9 +48,9 @@ from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives import hashes
|
from cryptography.hazmat.primitives import hashes
|
||||||
from dateutil import parser
|
from dateutil import parser
|
||||||
|
|
||||||
import seafileapi
|
|
||||||
|
|
||||||
from settings import DEBUG, DB_COMPANIES, PATHS
|
from settings import DEBUG, DB_COMPANIES, PATHS
|
||||||
|
|
||||||
|
from .cfdi_cert import SATCertificate
|
||||||
from .pacs import PACComercioDigital
|
from .pacs import PACComercioDigital
|
||||||
from .pac import Finkok as PACFinkok
|
from .pac import Finkok as PACFinkok
|
||||||
# ~ from .finkok import PACFinkok
|
# ~ from .finkok import PACFinkok
|
||||||
|
@ -492,29 +492,6 @@ def _backup_db(rfc, is_mv, url_seafile):
|
||||||
shutil.copy(path, path_target)
|
shutil.copy(path, path_target)
|
||||||
else:
|
else:
|
||||||
log.error('\tNo existe la carpeta compartida...')
|
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
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@ -629,14 +606,30 @@ def xml_cancel(xml, auth, cert, name):
|
||||||
return data, result
|
return data, result
|
||||||
|
|
||||||
|
|
||||||
def get_client_balance(auth, name):
|
def get_client_balance(auth):
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
return '-d'
|
return '-d'
|
||||||
|
|
||||||
pac = PACS[name]()
|
pac = PACS[auth['pac']]()
|
||||||
auth = {'usr': auth['USER'], 'pwd': auth['PASS']}
|
|
||||||
balance = pac.client_balance(auth)
|
balance = pac.client_balance(auth)
|
||||||
if pac.error:
|
if pac.error:
|
||||||
balance = '-e'
|
balance = '-e'
|
||||||
|
|
||||||
return balance
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ from controllers.main import (AppEmpresas,
|
||||||
AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco,
|
AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco,
|
||||||
AppMovimientosBanco, AppTickets, AppStudents, AppEmployees, AppNomina,
|
AppMovimientosBanco, AppTickets, AppStudents, AppEmployees, AppNomina,
|
||||||
AppInvoicePay, AppCfdiPay, AppSATBancos, AppSociosCuentasBanco,
|
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('/satformapago', AppSATFormaPago(db))
|
||||||
api.add_route('/socioscb', AppSociosCuentasBanco(db))
|
api.add_route('/socioscb', AppSociosCuentasBanco(db))
|
||||||
api.add_route('/leyendasfiscales', AppSATLeyendaFiscales(db))
|
api.add_route('/leyendasfiscales', AppSATLeyendaFiscales(db))
|
||||||
|
api.add_route('/cert', AppCert(db))
|
||||||
|
|
||||||
|
|
||||||
session_options = {
|
session_options = {
|
||||||
|
|
|
@ -471,6 +471,13 @@ class StorageEngine(object):
|
||||||
def sat_leyendas_fiscales_delete(self, values):
|
def sat_leyendas_fiscales_delete(self, values):
|
||||||
return main.SATLeyendasFiscales.remove(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
|
# Companies only in MV
|
||||||
def _get_empresas(self, values):
|
def _get_empresas(self, values):
|
||||||
return main.companies_get()
|
return main.companies_get()
|
||||||
|
|
|
@ -1165,19 +1165,35 @@ class Certificado(BaseModel):
|
||||||
return self.serie
|
return self.serie
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_cert(cls, is_fiel=False):
|
def _get_cert(cls, args):
|
||||||
return Certificado.get(Certificado.es_fiel==is_fiel)
|
obj = Certificado.get(Certificado.es_fiel==False)
|
||||||
|
data = {
|
||||||
@classmethod
|
|
||||||
def get_data(cls):
|
|
||||||
obj = cls.get_(cls)
|
|
||||||
row = {
|
|
||||||
'cert_rfc': obj.rfc,
|
'cert_rfc': obj.rfc,
|
||||||
'cert_serie': obj.serie,
|
'cert_serie': obj.serie,
|
||||||
'cert_desde': obj.desde,
|
'cert_desde': obj.desde,
|
||||||
'cert_hasta': obj.hasta,
|
'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):
|
def get_(cls):
|
||||||
return Certificado.select()[0]
|
return Certificado.select()[0]
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
from seafileapi.client import SeafileApiClient
|
|
||||||
|
|
||||||
def connect(server, username, password):
|
|
||||||
client = SeafileApiClient(server, username, password)
|
|
||||||
return client
|
|
|
@ -1,7 +0,0 @@
|
||||||
|
|
||||||
class SeafileAdmin(object):
|
|
||||||
def lists_users(self, maxcount=100):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def list_user_repos(self, username):
|
|
||||||
pass
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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__
|
|
|
@ -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
|
|
|
@ -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)
|
|
|
@ -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]
|
|
|
@ -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
|
|
|
@ -30,11 +30,6 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
DEFAULT_PASSWORD = 'salgueiro3.3'
|
DEFAULT_PASSWORD = 'salgueiro3.3'
|
||||||
|
|
||||||
try:
|
|
||||||
from conf import SEAFILE_SERVER
|
|
||||||
except ImportError:
|
|
||||||
SEAFILE_SERVER = {}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from conf import TITLE_APP
|
from conf import TITLE_APP
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
|
@ -19,6 +19,9 @@ var msg = ''
|
||||||
var tb_options = null
|
var tb_options = null
|
||||||
var tb_sat = null
|
var tb_sat = null
|
||||||
|
|
||||||
|
var file_cer = null
|
||||||
|
var file_key = null
|
||||||
|
|
||||||
|
|
||||||
var controllers = {
|
var controllers = {
|
||||||
init: function(){
|
init: function(){
|
||||||
|
@ -32,7 +35,9 @@ var controllers = {
|
||||||
$$('chk_escuela').attachEvent('onChange', chk_escuela_change)
|
$$('chk_escuela').attachEvent('onChange', chk_escuela_change)
|
||||||
$$('chk_ong').attachEvent('onChange', chk_ong_change)
|
$$('chk_ong').attachEvent('onChange', chk_ong_change)
|
||||||
$$('cmd_subir_certificado').attachEvent('onItemClick', cmd_subir_certificado_click)
|
$$('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)
|
$$('cmd_agregar_serie').attachEvent('onItemClick', cmd_agregar_serie_click)
|
||||||
$$('grid_folios').attachEvent('onItemClick', grid_folios_click)
|
$$('grid_folios').attachEvent('onItemClick', grid_folios_click)
|
||||||
$$('chk_folio_custom').attachEvent('onItemClick', chk_config_item_click)
|
$$('chk_folio_custom').attachEvent('onItemClick', chk_config_item_click)
|
||||||
|
@ -280,7 +285,7 @@ function get_emisor(){
|
||||||
function get_certificado(){
|
function get_certificado(){
|
||||||
var form = $$('form_cert')
|
var form = $$('form_cert')
|
||||||
|
|
||||||
webix.ajax().get("/values/cert", {}, {
|
webix.ajax().get("/cert", {'opt': 'cert'}, {
|
||||||
error: function(text, data, xhr) {
|
error: function(text, data, xhr) {
|
||||||
msg = 'Error al consultar'
|
msg = 'Error al consultar'
|
||||||
msg_error(msg)
|
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<BR><BR>¿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(){
|
function cmd_agregar_serie_click(){
|
||||||
var form = $$('form_folios')
|
var form = $$('form_folios')
|
||||||
var grid = $$('grid_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<BR><BR>¿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)
|
||||||
|
//~ }
|
||||||
|
//~ }
|
||||||
|
//~ })
|
||||||
|
//~ }
|
||||||
|
|
|
@ -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 = [
|
var emisor_certificado = [
|
||||||
{cols: [col_sello, col_fiel]},
|
{cols: [col_sello, col_fiel]},
|
||||||
{template: 'Cargar Certificado', type: 'section'},
|
{template: 'Cargar Certificado', type: 'section'},
|
||||||
{view: 'form', id: 'form_upload', rows: [
|
{view: 'form', id: 'form_upload', rows: [
|
||||||
{cols: [{},
|
{cols: [{},
|
||||||
{view: 'uploader', id: 'up_cert', autosend: false, link: 'lst_cert',
|
{view: 'uploader', id: 'up_cert', autosend: false, link: 'lst_cert',
|
||||||
value: 'Seleccionar certificado', upload: '/values/files'}, {}]},
|
value: 'Seleccionar certificado'}, {}]},
|
||||||
{cols: [{},
|
{cols: [{},
|
||||||
{view: 'list', id: 'lst_cert', name: 'certificado',
|
{view: 'list', id: 'lst_cert', name: 'certificado',
|
||||||
type: 'uploader', autoheight:true, borderless: true}, {}]},
|
type: 'uploader', autoheight:true, borderless: true}, {}]},
|
||||||
{cols: [{},
|
{cols: [{},
|
||||||
{view: 'text', id: 'txt_contra', name: 'contra',
|
{view: 'text', id: 'txt_contra', name: 'contra', value: '12345678a',
|
||||||
label: 'Contraseña KEY', labelPosition: 'top',
|
label: 'Contraseña KEY', labelPosition: 'top',
|
||||||
labelAlign: 'center', type: 'password', required: true}, {}]},
|
labelAlign: 'center', type: 'password', required: true}, {}]},
|
||||||
{cols: [{}, {view: 'button', id: 'cmd_subir_certificado',
|
{cols: [{}, {view: 'button', id: 'cmd_subir_certificado',
|
||||||
|
|
Loading…
Reference in New Issue