diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 5952af9..a511f5b 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -47,6 +47,7 @@ class AppLogin(object): session.invalidate() values = req.params values['rfc'] = values['rfc'].upper() + values['ip'] = req.remote_addr result, user = self._db.authenticate(values) if result['login']: session.save() diff --git a/source/app/controllers/utils.py b/source/app/controllers/utils.py index 5e2c779..8332596 100644 --- a/source/app/controllers/utils.py +++ b/source/app/controllers/utils.py @@ -18,12 +18,130 @@ import base64 import math +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.base import MIMEBase +from email.mime.text import MIMEText +from email import encoders +from email.utils import formatdate + +import requests from cryptography.fernet import Fernet from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes +TIMEOUT = 10 + + +class SendMail(object): + + def __init__(self, config): + self._config = config + self._server = None + self._error = '' + self._is_connect = self._login() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self): + hosts = ('gmail' in self._config['server'] or + 'outlook' in self._config['server']) + try: + if self._config['ssl'] and hosts: + self._server = smtplib.SMTP( + self._config['server'], + self._config['port'], timeout=TIMEOUT) + self._server.ehlo() + self._server.starttls() + self._server.ehlo() + elif self._config['ssl']: + self._server = smtplib.SMTP_SSL( + self._config['server'], + self._config['port'], timeout=TIMEOUT) + self._server.ehlo() + else: + self._server = smtplib.SMTP( + self._config['server'], + self._config['port'], timeout=TIMEOUT) + self._server.login(self._config['user'], self._config['pass']) + return True + except smtplib.SMTPAuthenticationError as e: + if '535' in str(e): + self._error = 'Nombre de usuario o contraseña inválidos' + return False + # ~ print (e) + if '534' in str(e) and 'gmail' in self._config['server']: + self._error = 'Necesitas activar el acceso a otras ' \ + 'aplicaciones en tu cuenta de GMail' + return False + except smtplib.SMTPException as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return + + def send(self, options): + try: + message = MIMEMultipart() + message['From'] = self._config['user'] + message['To'] = options['to'] + message['CC'] = options.get('copy', '') + message['Subject'] = options['subject'] + message['Date'] = formatdate(localtime=True) + if options.get('confirm', False): + message['Disposition-Notification-To'] = message['From'] + message.attach(MIMEText(options['message'], 'html')) + for f in options.get('files', ()): + part = MIMEBase('application', 'octet-stream') + if isinstance(f[0], str): + part.set_payload(f[0].encode('utf-8')) + else: + part.set_payload(f[0]) + encoders.encode_base64(part) + part.add_header( + 'Content-Disposition', + "attachment; filename={}".format(f[1])) + message.attach(part) + + receivers = options['to'].split(',') + message['CC'].split(',') + self._server.sendmail( + self._config['user'], receivers, message.as_string()) + return '' + except Exception as e: + return str(e) + + def close(self): + try: + self._server.quit() + except: + pass + return + + +def send_mail(data): + msg = '' + ok = True + server = SendMail(data['server']) + if server.is_connect: + msg = server.send(data['mail']) + else: + msg = server.error + ok = False + server.close() + + return {'ok': ok, 'msg': msg} + + def round_up(value): return int(math.ceil(value)) @@ -45,3 +163,10 @@ def decrypt(data, password): return f.decrypt(data.encode()).decode() +def to_bool(value): + return bool(int(value)) + + +def get_url(url): + r = requests.get(url).text + return r diff --git a/source/app/models/main.py b/source/app/models/main.py index bd34878..f0e47e9 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -460,6 +460,17 @@ class Configuracion(BaseModel): values = {r.clave: r.valor for r in data} return values + def _get_admin_config_users(self): + fields = ( + 'chk_users_notify_access', + ) + data = (Configuracion + .select() + .where(Configuracion.clave.in_(fields)) + ) + values = {r.clave: util.get_bool(r.valor) for r in data} + return values + @classmethod def get_(cls, keys): if isinstance(keys, str): @@ -477,6 +488,7 @@ class Configuracion(BaseModel): 'complements', 'folios', 'correo', + 'admin_config_users', ) opt = keys['fields'] if opt in options: @@ -8570,6 +8582,50 @@ def _save_log(user, action, table): return +@util.run_in_thread +def _send_notify_access(args): + admins = (Usuarios + .select(Usuarios.correo) + .where(Usuarios.es_admin==True) + .scalar(as_tuple=True)) + + if not admins: + return + + config = Configuracion.get_({'fields': 'correo'}) + contra = Configuracion.get_('correo_contra') + if not config: + return + + user = args['usuario'] + rfc = args['rfc'] + ip = args['ip'] + + url = f"http://ip-api.com/line/{ip}?fields=city" + city = utils.get_url(url) + message = f"Desde la IP: {ip} en: {city}" + + server = { + 'server': config['correo_servidor'], + 'port': config['correo_puerto'], + 'ssl': utils.to_bool(config['correo_ssl']), + 'user': config['correo_usuario'], + 'pass': utils.decrypt(contra, rfc), + } + mail = { + 'to': ','.join(admins), + 'subject': f"Usuario {user} identificado", + 'message': message, + } + data= { + 'server': server, + 'mail': mail, + } + result = utils.send_mail(data) + + return + + def authenticate(args): respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''} values = util.get_con(args['rfc']) @@ -8594,7 +8650,11 @@ def authenticate(args): respuesta['login'] = True respuesta['user'] = str(obj) respuesta['super'] = obj.es_superusuario - #~ respuesta['admin'] = obj.es_superusuario or obj.es_admin + + notify_access = Configuracion.get_bool('chk_users_notify_access') + if notify_access: + _send_notify_access(args) + return respuesta, obj diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index af868bc..d533bdc 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -59,11 +59,13 @@ var controllers = { $$('grid_admin_unidades').attachEvent('onItemClick', grid_admin_unidades_click) $$('grid_moneda_found').attachEvent('onValueSuggest', grid_moneda_found_click) $$('cmd_agregar_impuesto').attachEvent('onItemClick', cmd_agregar_impuesto_click) + //~ Usuarios $$('cmd_usuario_agregar').attachEvent('onItemClick', cmd_usuario_agregar_click) $$('grid_usuarios').attachEvent('onItemClick', grid_usuarios_click) $$('grid_usuarios').attachEvent('onCheck', grid_usuarios_on_check) $$('grid_usuarios').attachEvent('onItemDblClick', grid_usuarios_double_click) + $$('chk_users_notify_access').attachEvent('onItemClick', chk_config_item_click) admin_ui_windows.init() //~ Opciones @@ -408,9 +410,22 @@ function get_admin_usos_cfdi(){ function get_admin_usuarios(){ webix.ajax().sync().get('/values/allusuarios', function(text, data){ - var values = data.json() + var rows = data.json() $$('grid_usuarios').clearAll() - $$('grid_usuarios').parse(values, 'json') + $$('grid_usuarios').parse(rows) + }) + + webix.ajax().get('/config', {'fields': 'admin_config_users'}, { + error: function(text, data, xhr) { + msg = 'Error al consultar' + msg_error(msg) + }, + success: function(text, data, xhr) { + var values = data.json() + Object.keys(values).forEach(function(key){ + $$(key).setValue(values[key]) + }) + } }) } @@ -447,7 +462,6 @@ function get_config_values(opt){ }, success: function(text, data, xhr) { var values = data.json() - //~ showvar(values) Object.keys(values).forEach(function(key){ $$(key).setValue(values[key]) }) diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index d864799..405e968 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -1219,6 +1219,10 @@ var usuarios_admin = [ {maxHeight: 20}, {template: 'Usuarios Registrados', type: 'section'}, {cols: [{maxWidth: 10}, grid_usuarios, {maxWidth: 10}]}, + {maxHeight: 20}, + {template: 'Opciones', type: 'section'}, + {cols: [{view: 'checkbox', id: 'chk_users_notify_access', labelWidth: 15, + labelRight: 'Notificar accesos al sistema (solo a administradores)'}]}, {}, ]