Agregar certificado
This commit is contained in:
parent
361aa131d3
commit
f525ced698
|
@ -1,7 +1,9 @@
|
||||||
falcon
|
falcon
|
||||||
|
falcon-multipart
|
||||||
Beaker
|
Beaker
|
||||||
Mako
|
Mako
|
||||||
peewee
|
peewee
|
||||||
click
|
click
|
||||||
logbook
|
logbook
|
||||||
bcrypt
|
bcrypt
|
||||||
|
python-dateutil
|
||||||
|
|
|
@ -70,6 +70,17 @@ class AppValues(object):
|
||||||
req.context['result'] = self._db.get_values(table, values)
|
req.context['result'] = self._db.get_values(table, values)
|
||||||
resp.status = falcon.HTTP_200
|
resp.status = falcon.HTTP_200
|
||||||
|
|
||||||
|
def on_post(self, req, resp, table):
|
||||||
|
file_object = req.get_param('upload')
|
||||||
|
if file_object is None:
|
||||||
|
session = req.env['beaker.session']
|
||||||
|
values = req.params
|
||||||
|
req.context['result'] = self._db.validate_cert(values, session)
|
||||||
|
else:
|
||||||
|
req.context['result'] = self._db.add_cert(file_object)
|
||||||
|
resp.status = falcon.HTTP_200
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AppPartners(object):
|
class AppPartners(object):
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,20 @@
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import getpass
|
import getpass
|
||||||
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
import unicodedata
|
import unicodedata
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
#~ import bcrypt
|
from dateutil import parser
|
||||||
|
|
||||||
from settings import log, template_lookup, COMPANIES, DB_SAT
|
from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT
|
||||||
|
|
||||||
|
|
||||||
#~ def _get_hash(password):
|
#~ def _get_hash(password):
|
||||||
|
@ -23,6 +26,17 @@ from settings import log, template_lookup, COMPANIES, DB_SAT
|
||||||
#~ return bcrypt.hashpw(password.encode(), hashed.encode()) == hashed.encode()
|
#~ return bcrypt.hashpw(password.encode(), hashed.encode()) == hashed.encode()
|
||||||
|
|
||||||
|
|
||||||
|
def _call(args):
|
||||||
|
return subprocess.check_output(args, shell=True).decode()
|
||||||
|
|
||||||
|
|
||||||
|
def _save_temp(data, modo='wb'):
|
||||||
|
path = tempfile.mkstemp()[1]
|
||||||
|
with open(path, modo) as f:
|
||||||
|
f.write(data)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
def get_pass():
|
def get_pass():
|
||||||
password = getpass.getpass('Introduce la contraseña: ')
|
password = getpass.getpass('Introduce la contraseña: ')
|
||||||
pass2 = getpass.getpass('Confirma la contraseña: ')
|
pass2 = getpass.getpass('Confirma la contraseña: ')
|
||||||
|
@ -177,3 +191,148 @@ def to_slug(string):
|
||||||
.encode('ascii', 'ignore')
|
.encode('ascii', 'ignore')
|
||||||
.decode('ascii').lower())
|
.decode('ascii').lower())
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class Certificado(object):
|
||||||
|
|
||||||
|
def __init__(self, key, cer):
|
||||||
|
self._key = key
|
||||||
|
self._cer = cer
|
||||||
|
self._modulus = ''
|
||||||
|
self._save_files()
|
||||||
|
self.error = ''
|
||||||
|
|
||||||
|
def _save_files(self):
|
||||||
|
try:
|
||||||
|
self._path_key = _save_temp(self._key)
|
||||||
|
self._path_cer = _save_temp(self._cer)
|
||||||
|
except:
|
||||||
|
self._path_key = ''
|
||||||
|
self._path_cer = ''
|
||||||
|
return
|
||||||
|
|
||||||
|
def _kill(self, path):
|
||||||
|
try:
|
||||||
|
os.remove(path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
def _get_info_cer(self, session_rfc):
|
||||||
|
data = {}
|
||||||
|
args = 'openssl x509 -inform DER -in {}'
|
||||||
|
try:
|
||||||
|
cer_pem = _call(args.format(self._path_cer))
|
||||||
|
except Exception as e:
|
||||||
|
self.error = 'No se pudo convertir el CER en PEM'
|
||||||
|
return data
|
||||||
|
|
||||||
|
args = 'openssl enc -base64 -in {}'
|
||||||
|
try:
|
||||||
|
cer_txt = _call(args.format(self._path_cer))
|
||||||
|
except Exception as e:
|
||||||
|
self.error = 'No se pudo convertir el CER en TXT'
|
||||||
|
return data
|
||||||
|
|
||||||
|
args = 'openssl x509 -inform DER -in {} -noout -{}'
|
||||||
|
try:
|
||||||
|
result = _call(args.format(self._path_cer, 'purpose')).split('\n')[3]
|
||||||
|
except Exception as e:
|
||||||
|
self.error = 'No se puede saber si es FIEL'
|
||||||
|
return data
|
||||||
|
|
||||||
|
if result == 'SSL server : No':
|
||||||
|
self.error = 'El certificado es FIEL'
|
||||||
|
return data
|
||||||
|
|
||||||
|
result = _call(args.format(self._path_cer, 'serial'))
|
||||||
|
serie = result.split('=')[1].split('\n')[0][1::2]
|
||||||
|
result = _call(args.format(self._path_cer, 'subject'))
|
||||||
|
rfc = result.split('=')[5].split('/')[0].strip()
|
||||||
|
|
||||||
|
if not DEBUG:
|
||||||
|
if not rfc == session_rfc:
|
||||||
|
self.error = 'El RFC del certificado no corresponde.'
|
||||||
|
return data
|
||||||
|
|
||||||
|
dates = _call(args.format(self._path_cer, 'dates')).split('\n')
|
||||||
|
desde = parser.parse(dates[0].split('=')[1])
|
||||||
|
hasta = parser.parse(dates[1].split('=')[1])
|
||||||
|
self._modulus = _call(args.format(self._path_cer, 'modulus'))
|
||||||
|
|
||||||
|
data['cer'] = self._cer
|
||||||
|
data['cer_tmp'] = None
|
||||||
|
data['cer_pem'] = cer_pem
|
||||||
|
data['cer_txt'] = cer_txt.replace('\n', '')
|
||||||
|
data['serie'] = serie
|
||||||
|
data['rfc'] = rfc
|
||||||
|
data['desde'] = desde
|
||||||
|
data['hasta'] = hasta
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _get_p12(self, password, rfc):
|
||||||
|
tmp_cer = tempfile.mkstemp()[1]
|
||||||
|
tmp_key = tempfile.mkstemp()[1]
|
||||||
|
tmp_p12 = tempfile.mkstemp()[1]
|
||||||
|
|
||||||
|
args = 'openssl x509 -inform DER -in "{}" -out "{}"'
|
||||||
|
_call(args.format(self._path_cer, tmp_cer))
|
||||||
|
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:{} -out "{}"'
|
||||||
|
_call(args.format(self._path_key, password, tmp_key))
|
||||||
|
|
||||||
|
args = 'openssl pkcs12 -export -in "{}" -inkey "{}" -name "{}" -passout ' \
|
||||||
|
'pass:"{}" -out "{}"'
|
||||||
|
_call(args.format(tmp_cer, tmp_key, rfc,
|
||||||
|
hashlib.md5(rfc.encode()).hexdigest(), tmp_p12))
|
||||||
|
data = open(tmp_p12, 'rb').read()
|
||||||
|
|
||||||
|
self._kill(tmp_cer)
|
||||||
|
self._kill(tmp_key)
|
||||||
|
self._kill(tmp_p12)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _get_info_key(self, password, rfc):
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:{}'
|
||||||
|
try:
|
||||||
|
result = _call(args.format(self._path_key, password))
|
||||||
|
except Exception as e:
|
||||||
|
self.error = 'Contraseña incorrecta'
|
||||||
|
return data
|
||||||
|
|
||||||
|
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:{} | ' \
|
||||||
|
'openssl rsa -noout -modulus'
|
||||||
|
mod_key = _call(args.format(self._path_key, password))
|
||||||
|
|
||||||
|
if self._modulus != mod_key:
|
||||||
|
self.error = 'Los archivos no son pareja'
|
||||||
|
return data
|
||||||
|
|
||||||
|
args = 'openssl pkcs8 -inform DER -in "{}" -passin pass:{} | ' \
|
||||||
|
'openssl rsa -des3 -passout pass:{}'.format(
|
||||||
|
self._path_key, password, hashlib.md5(rfc.encode()).hexdigest())
|
||||||
|
key_enc = _call(args)
|
||||||
|
|
||||||
|
data['key'] = self._key
|
||||||
|
data['key_tmp'] = None
|
||||||
|
data['key_enc'] = key_enc
|
||||||
|
data['p12'] = self._get_p12(password, rfc)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def validate(self, password, rfc):
|
||||||
|
if not self._path_key or not self._path_cer:
|
||||||
|
self.error = 'Error al cargar el certificado'
|
||||||
|
return {}
|
||||||
|
|
||||||
|
data = self._get_info_cer(rfc)
|
||||||
|
llave = self._get_info_key(password, rfc)
|
||||||
|
if not llave:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
data.update(llave)
|
||||||
|
|
||||||
|
self._kill(self._path_key)
|
||||||
|
self._kill(self._path_cer)
|
||||||
|
return data
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import falcon
|
import falcon
|
||||||
|
from falcon_multipart.middleware import MultipartMiddleware
|
||||||
from beaker.middleware import SessionMiddleware
|
from beaker.middleware import SessionMiddleware
|
||||||
|
|
||||||
from middleware import (
|
from middleware import (
|
||||||
|
@ -20,8 +21,12 @@ from settings import DEBUG
|
||||||
|
|
||||||
db = StorageEngine()
|
db = StorageEngine()
|
||||||
|
|
||||||
api = falcon.API(
|
api = falcon.API(middleware=[
|
||||||
middleware=[AuthMiddleware(), JSONTranslator(), ConnectionMiddleware()])
|
AuthMiddleware(),
|
||||||
|
JSONTranslator(),
|
||||||
|
ConnectionMiddleware(),
|
||||||
|
MultipartMiddleware(),
|
||||||
|
])
|
||||||
api.req_options.auto_parse_form_urlencoded = True
|
api.req_options.auto_parse_form_urlencoded = True
|
||||||
api.add_sink(handle_404, '')
|
api.add_sink(handle_404, '')
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,15 @@ class StorageEngine(object):
|
||||||
def get_values(self, table, values=None):
|
def get_values(self, table, values=None):
|
||||||
return getattr(self, '_get_{}'.format(table))(values)
|
return getattr(self, '_get_{}'.format(table))(values)
|
||||||
|
|
||||||
|
def add_cert(self, file_object):
|
||||||
|
return main.Certificado.add(file_object)
|
||||||
|
|
||||||
|
def validate_cert(self, values, session):
|
||||||
|
return main.Certificado.validate(values, session)
|
||||||
|
|
||||||
|
def _get_cert(self, values):
|
||||||
|
return main.Certificado.get_data()
|
||||||
|
|
||||||
def _get_cp(self, values):
|
def _get_cp(self, values):
|
||||||
return main.get_cp(values['cp'])
|
return main.get_cp(values['cp'])
|
||||||
|
|
||||||
|
|
|
@ -238,20 +238,77 @@ class Emisor(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class Certificado(BaseModel):
|
class Certificado(BaseModel):
|
||||||
key = BlobField()
|
key = BlobField(null=True)
|
||||||
|
key_tmp = BlobField(null=True)
|
||||||
key_enc = TextField(default='')
|
key_enc = TextField(default='')
|
||||||
cer = BlobField()
|
cer = BlobField(null=True)
|
||||||
|
cer_tmp = BlobField(null=True)
|
||||||
cer_pem = TextField(default='')
|
cer_pem = TextField(default='')
|
||||||
cer_txt = TextField(default='')
|
cer_txt = TextField(default='')
|
||||||
p12 = BlobField()
|
p12 = BlobField(null=True)
|
||||||
serie = TextField(default='')
|
serie = TextField(default='')
|
||||||
rfc = TextField(default='')
|
rfc = TextField(default='')
|
||||||
desde = DateTimeField()
|
desde = DateTimeField(null=True)
|
||||||
hasta = DateTimeField()
|
hasta = DateTimeField(null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.serie
|
return self.serie
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_data(cls):
|
||||||
|
obj = cls.get_(cls)
|
||||||
|
row = {
|
||||||
|
'cert_rfc': obj.rfc,
|
||||||
|
'cert_serie': obj.serie,
|
||||||
|
'cert_desde': obj.desde,
|
||||||
|
'cert_hasta': obj.hasta,
|
||||||
|
}
|
||||||
|
return row
|
||||||
|
|
||||||
|
def get_(cls):
|
||||||
|
if Certificado.select().count():
|
||||||
|
obj = Certificado.select()[0]
|
||||||
|
else:
|
||||||
|
obj = Certificado()
|
||||||
|
return obj
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add(cls, file_object):
|
||||||
|
obj = cls.get_(cls)
|
||||||
|
if file_object.filename.endswith('key'):
|
||||||
|
obj.key_tmp = file_object.file.read()
|
||||||
|
elif file_object.filename.endswith('cer'):
|
||||||
|
obj.cer_tmp = file_object.file.read()
|
||||||
|
obj.save()
|
||||||
|
return {'status': 'server'}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate(cls, values, session):
|
||||||
|
row = {}
|
||||||
|
result = False
|
||||||
|
obj = cls.get_(cls)
|
||||||
|
cert = util.Certificado(obj.key_tmp, obj.cer_tmp)
|
||||||
|
data = cert.validate(values['contra'], session['rfc'])
|
||||||
|
if data:
|
||||||
|
msg = 'Certificado guardado correctamente'
|
||||||
|
q = Certificado.update(**data).where(Certificado.id==obj.id)
|
||||||
|
if q.execute():
|
||||||
|
result = True
|
||||||
|
row = {
|
||||||
|
'cert_rfc': data['rfc'],
|
||||||
|
'cert_serie': data['serie'],
|
||||||
|
'cert_desde': data['desde'],
|
||||||
|
'cert_hasta': data['hasta'],
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
msg = cert.error
|
||||||
|
obj.key_tmp = None
|
||||||
|
obj.cer_tmp = None
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
return {'ok': result, 'msg': msg, 'data': row}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Folios(BaseModel):
|
class Folios(BaseModel):
|
||||||
serie = TextField(unique=True)
|
serie = TextField(unique=True)
|
||||||
|
|
|
@ -13,7 +13,8 @@ var controllers = {
|
||||||
$$('emisor_cp').attachEvent('onTimedKeyPress', emisor_postal_code_key_up)
|
$$('emisor_cp').attachEvent('onTimedKeyPress', emisor_postal_code_key_up)
|
||||||
$$('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)
|
||||||
|
$$('up_cert').attachEvent('onUploadComplete', up_cert_upload_complete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,11 +142,29 @@ function get_emisor(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function get_certificado(){
|
||||||
|
var form = $$('form_cert')
|
||||||
|
|
||||||
|
webix.ajax().get("/values/cert", {}, {
|
||||||
|
error: function(text, data, xhr) {
|
||||||
|
msg = 'Error al consultar'
|
||||||
|
msg_error(msg)
|
||||||
|
},
|
||||||
|
success: function(text, data, xhr) {
|
||||||
|
var values = data.json()
|
||||||
|
form.setValues(values)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function multi_admin_change(prevID, nextID){
|
function multi_admin_change(prevID, nextID){
|
||||||
//~ webix.message(nextID)
|
//~ webix.message(nextID)
|
||||||
if(nextID == 'app_emisor'){
|
if(nextID == 'app_emisor'){
|
||||||
$$('tab_emisor').setValue('Datos Fiscales')
|
$$('tab_emisor').setValue('Datos Fiscales')
|
||||||
get_emisor()
|
get_emisor()
|
||||||
|
get_certificado()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,3 +240,99 @@ function chk_ong_change(new_value, old_value){
|
||||||
$$('ong_fecha_dof').disable()
|
$$('ong_fecha_dof').disable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 rfc = $$('form_cert').getValues()['cert_rfc']
|
||||||
|
if(rfc){
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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_sucess(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_sucess(values.msg)
|
||||||
|
}else{
|
||||||
|
msg_error(values.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -104,15 +104,15 @@ var emisor_certificado = [
|
||||||
{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/cert'}, {}]},
|
value: 'Seleccionar certificado', upload: '/values/files'}, {}]},
|
||||||
{cols: [{},
|
{cols: [{},
|
||||||
{view: 'list', id: 'lst_cert', type: 'uploader', autoheight:true,
|
{view: 'list', id: 'lst_cert', name: 'certificado',
|
||||||
borderless: true}, {}]},
|
type: 'uploader', autoheight:true, borderless: true}, {}]},
|
||||||
{cols: [{},
|
{cols: [{},
|
||||||
{view: 'text', id: 'txt_contra', label: 'Contraseña KEY',
|
{view: 'text', id: 'txt_contra', name: 'contra',
|
||||||
labelPosition: 'top', labelAlign: 'center', type: 'password',
|
label: 'Contraseña KEY', labelPosition: 'top',
|
||||||
required: true}, {}]},
|
labelAlign: 'center', type: 'password', required: true}, {}]},
|
||||||
{cols: [{}, {view: 'button', id: 'cmd_subir_certificado',
|
{cols: [{}, {view: 'button', id: 'cmd_subir_certificado',
|
||||||
label: 'Subir certificado'}, {}]},
|
label: 'Subir certificado'}, {}]},
|
||||||
]},
|
]},
|
||||||
|
|
Loading…
Reference in New Issue