Merge branch 'master' of https://gitlab.com/m3f_usm/admin_transporte/backend into develop/Ronald

Obteniendo ultima version
francisco/photos
Ronald Morales 2024-01-29 20:23:20 -03:00
commit b1041b2a2f
10 changed files with 240 additions and 82 deletions

19
.env.develop 100644
View File

@ -0,0 +1,19 @@
# Conexion con base datos principal
DBHOST=db
DBPORT=5432
DBNAME=transporte
DBSCHEMA=public
DBUSER=postgres
DBPASS=password
# Conexion con base datos redis
DB_REDIS_HOST=dbproto
DB_REDIS_PORT=6379
# Datos de emisor de correos
SMTP_HOST=smtp-mail.outlook.com
SMTP_PORT=587
SMTP_PROTOCOL=tls
SMTP_USER=francisco.sandoval@outlook.cl
SMTP_PASS=ppcsrdsvecdewnfl
SMTP_FROM='"Sistema Transporte" <francisco.sandoval@outlook.cl>'

View File

@ -2,11 +2,10 @@ from django.urls import resolve
from django.http import HttpResponse from django.http import HttpResponse
from .models import Usuario, Persona from .models import Usuario, Persona
from decouple import config from decouple import config
from project.settings import SECRET_KEY
import jwt import jwt
import logging import logging
private_key = config('SECRET_JWT')
class ApiMiddleware: class ApiMiddleware:
def __init__(self, get_response): def __init__(self, get_response):
self.get_response = get_response self.get_response = get_response
@ -17,11 +16,6 @@ class ApiMiddleware:
response = self.get_response(request) response = self.get_response(request)
return response return response
# se omite esta regla en login
if request.path == '/api/auth/' and request.method == 'POST':
response = self.get_response(request)
return response
match = resolve(request.path) match = resolve(request.path)
logging.error(match) logging.error(match)
@ -30,6 +24,22 @@ class ApiMiddleware:
response = self.get_response(request) response = self.get_response(request)
return response return response
if match.url_name == 'auth_login' and request.method == 'POST':
response = self.get_response(request)
return response
if match.url_name == 'auth_recuperar':
response = self.get_response(request)
return response
if match.url_name == 'auth_info':
response = self.get_response(request)
return response
if match.url_name == 'auth_contrasena':
response = self.get_response(request)
return response
# se omite esta regla al mostrar informacion publica de paradero # se omite esta regla al mostrar informacion publica de paradero
if match.url_name == 'paradero-info-public' and request.method == 'GET': if match.url_name == 'paradero-info-public' and request.method == 'GET':
response = self.get_response(request) response = self.get_response(request)
@ -42,7 +52,7 @@ class ApiMiddleware:
token = authorization[1] token = authorization[1]
try: try:
decoded = jwt.decode(token, private_key, algorithms=["HS256"]) decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
except jwt.ExpiredSignatureError: except jwt.ExpiredSignatureError:
return HttpResponse('token ya no es valido', status = 400) return HttpResponse('token ya no es valido', status = 400)
except jwt.InvalidTokenError: except jwt.InvalidTokenError:

View File

@ -369,24 +369,13 @@ class Usuario(models.Model):
vigente = models.BooleanField(blank=True, null=True) vigente = models.BooleanField(blank=True, null=True)
superuser = models.BooleanField(blank=True, null=True) superuser = models.BooleanField(blank=True, null=True)
id_rol = models.ForeignKey(Rol, models.DO_NOTHING, db_column='id_rol', blank=False, null=False) id_rol = models.ForeignKey(Rol, models.DO_NOTHING, db_column='id_rol', blank=False, null=False)
clave = models.CharField(max_length=100, blank=True, null=True)
class Meta: class Meta:
managed = False managed = False
db_table = 'usuario' db_table = 'usuario'
class UsuarioClave(models.Model):
login = models.OneToOneField(Usuario, models.DO_NOTHING, db_column='login', primary_key=True)
clave = models.CharField(max_length=60, blank=True, null=True)
clave_anterior = models.CharField(max_length=60, blank=True, null=True)
fecha_modificacion = models.DateField(blank=True, null=True)
codigo = models.DecimalField(max_digits=8, decimal_places=0, blank=True, null=True)
class Meta:
managed = False
db_table = 'usuario_clave'
class Vehiculo(models.Model): class Vehiculo(models.Model):
ppu = models.CharField(primary_key=True, max_length=10) ppu = models.CharField(primary_key=True, max_length=10)
id_tipo_vehiculo = models.ForeignKey(TipoVehiculo, models.DO_NOTHING, db_column='id_tipo_vehiculo', blank=True, null=True) id_tipo_vehiculo = models.ForeignKey(TipoVehiculo, models.DO_NOTHING, db_column='id_tipo_vehiculo', blank=True, null=True)

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div style="text-align: center;">
<p><strong>Estimado/a {{ nombre }}</strong></p>
<p>Para crear una nueva contraseña, es necesario que ingrese el siguiente código:
<strong style="font-size: 1.5rem;">{{ codigo }}</strong>
</p>
<p>Pinche el siguiente botón para crear una nueva contraseña.</p>
<p>
<a href="{{ vinculo }}" target="_blank" style="background: steelblue; color: #fff; padding: 1rem;">
CREAR NUEVA CONTRASEÑA
</a>
</p>
</div>
</body>
</html>

View File

@ -34,7 +34,10 @@ router.register('roles-lineas', rol_linea.RolLineaViewSet, basename='rol_linea')
urlpatterns = [ urlpatterns = [
path('', include(router.urls)), path('', include(router.urls)),
path('auth/', auth.jwt_login, name='auth'), path('auth/', auth.jwt_login, name='auth_login'),
path('auth/recuperar/', auth.recuperar, name='auth_recuperar'),
path('auth/info/', auth.info_token, name='auth_info'),
path('auth/nueva-contrasena/', auth.nueva_contrasena, name='auth_contrasena'),
path('mapas/paraderos/', mapa.paraderos, name='mapa-paraderos'), path('mapas/paraderos/', mapa.paraderos, name='mapa-paraderos'),
path('mapas/rutas/', mapa.rutas, name='mapa-rutas'), path('mapas/rutas/', mapa.rutas, name='mapa-rutas'),
path('upload/zip/', upload.upload_zip, name='upload_zip'), path('upload/zip/', upload.upload_zip, name='upload_zip'),

View File

@ -1,18 +1,22 @@
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse from django.contrib.auth.hashers import check_password, make_password
from django.http import JsonResponse from django.http import HttpResponse, JsonResponse
from rest_framework.decorators import action, api_view, schema from rest_framework.decorators import action, api_view, schema
from project.settings import SECRET_KEY, EMAIL_HOST
from .. import models, schemas from api import models, schemas
from datetime import datetime, timedelta
from decouple import config from decouple import config
import json import json
import jwt import jwt
from datetime import datetime, timedelta
import logging import logging
import random
private_key = config('SECRET_JWT') from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.template.loader import get_template
# Views jwt # Views jwt
@csrf_exempt @csrf_exempt
@ -21,26 +25,27 @@ private_key = config('SECRET_JWT')
@schema(schemas.AuthSchema()) @schema(schemas.AuthSchema())
def jwt_login(request): def jwt_login(request):
if request.method == 'POST': if request.method == 'POST':
count = models.Usuario.objects.filter(vigente = True).count() # validar rut y password
logging.error(f'count usuario vigente = {count}')
# validar username y password
input = json.loads(request.body) input = json.loads(request.body)
username = input['username'] rut = input['rut'].replace('.','').replace('-','')
password = input['password']
if rut != '0':
dv = rut[-1].upper()
rut = rut[:-1]
usuario = None usuario = None
if count > 0: if rut == '0' and password == '0':
usuario = models.Usuario.objects.filter(login = username, vigente = True).values().first()
elif username == '0' and password == '0':
usuario = { 'login': '0', 'clave': '0' } usuario = { 'login': '0', 'clave': '0' }
if not usuario: # solo se permite usuario 0 si no existen usuarios vigentes
count = models.Usuario.objects.filter(vigente = True).count()
if count > 0:
return HttpResponse('Acceso no valido', status=400) return HttpResponse('Acceso no valido', status=400)
else:
usuario = models.Usuario.objects.filter(vigente=1, rut__rut=rut, rut__dv=dv).values().first()
if username != '0': if not check_password(input['password'], usuario['clave']):
clave = models.UsuarioClave.objects.filter(login = username).first()
if not clave or clave.clave != password:
return HttpResponse('Acceso no valido', status=400) return HttpResponse('Acceso no valido', status=400)
ahora = datetime.utcnow() ahora = datetime.utcnow()
@ -52,7 +57,132 @@ def jwt_login(request):
'exp': manana, # ahora + timedelta(minutes=60), 'exp': manana, # ahora + timedelta(minutes=60),
'login': usuario['login'] 'login': usuario['login']
} }
token = jwt.encode(payload, private_key, algorithm="HS256") token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
return JsonResponse({ 'token': token }) return JsonResponse({ 'token': token })
elif request.method == 'GET': elif request.method == 'GET':
return JsonResponse(request.jwt_info) return JsonResponse(request.jwt_info)
@csrf_exempt
@action(detail=False, methods=['post'])
@api_view(['POST'])
def recuperar(request):
input = json.loads(request.body)
rut = input['rut'].replace('.','').replace('-','')
dv = rut[-1].upper()
rut = rut[:-1]
persona = models.Persona.objects.filter(rut=rut, dv=dv).first()
usuario = models.Usuario.objects.filter(rut=rut, vigente=True).first()
if usuario == None or persona == None:
return HttpResponse('Acceso no valido', status=400)
if persona.email != input['email'].lower():
return HttpResponse('Acceso no valido', status=400)
codigo_aleatorio = random.randint(100000, 999999)
ahora = datetime.utcnow()
expira = ahora + timedelta(minutes=5)
payload = {
'iat': ahora,
'exp': expira,
'rut': f'{persona.rut}',
'codigo': codigo_aleatorio
}
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
vinculo = f"{http_referer(request)}/?s={token}#/new-password"
exito = enviar_correo(persona.email, 'Recuperar acceso', {
'nombre': f'{persona.nombres} {persona.apellido_a} {persona.apellido_b}',
'codigo': codigo_aleatorio,
'vinculo': vinculo
})
return JsonResponse({ 'ok': exito, 'HTTP_REFERER': request.META['HTTP_REFERER'] })
@csrf_exempt
@action(detail=False, methods=['post'])
@api_view(['POST'])
def info_token(request):
input = json.loads(request.body)
token = input['token']
try:
decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
persona = models.Persona.objects.filter(rut=decoded['rut']).first()
return JsonResponse({
'nombres': persona.nombres,
'apellido_a': persona.apellido_a,
'apellido_b': persona.apellido_b
})
except jwt.ExpiredSignatureError:
return HttpResponse('token ya no es valido', status = 400)
except jwt.InvalidTokenError:
return HttpResponse('token es invalido', status = 400)
@csrf_exempt
@action(detail=False, methods=['post'])
@api_view(['POST'])
def nueva_contrasena(request):
input = json.loads(request.body)
token = input['token']
try:
decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
if f"{decoded['codigo']}" != input['codigo']:
return HttpResponse('código es invalido', status = 400)
usuario = models.Usuario.objects.filter(rut = decoded['rut']).first()
if usuario == None:
return HttpResponse('Usuario no encontrado', status = 400)
usuario.clave = make_password(input['password'])
usuario.save()
return JsonResponse({ 'ok': True })
except jwt.ExpiredSignatureError:
return HttpResponse('token ya no es valido', status = 400)
except jwt.InvalidTokenError:
return HttpResponse('token es invalido', status = 400)
except Exception as e:
logging.error(e)
return HttpResponse('error al cambiar contraseña', status = 500)
def enviar_correo(destinatario, asunto, contenido):
try:
template = get_template('correo_recuperar.html') # Ruta al template del correo
contenido_renderizado = template.render(contenido)
mensaje = EmailMultiAlternatives(asunto, '', settings.EMAIL_HOST_USER, [destinatario])
mensaje.attach_alternative(contenido_renderizado, 'text/html')
mensaje.send()
return True
except Exception as e:
print(f'EMAIL_HOST: {EMAIL_HOST}', flush=True)
print(f'ERROR: {e}', flush=True)
return False
def http_referer(request):
if 'HTTP_REFERER' in request.META:
referer = request.META['HTTP_REFERER']
else:
protocol = request.scheme
host = request.META['HTTP_HOST']
port = request.META['SERVER_PORT']
referer = f'{protocol}://{host}'
return referer

View File

@ -28,7 +28,7 @@ class ParaderoViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get'], url_path='info-public/(?P<pk>\S+)') @action(detail=False, methods=['get'], url_path='info-public/(?P<pk>\S+)')
def info_public(self, request, pk=None): def info_public(self, request, pk=None):
if hasattr(request.META,'HTTP_REFERER'): if 'HTTP_REFERER' in request.META:
referer = request.META['HTTP_REFERER'] referer = request.META['HTTP_REFERER']
else: else:
protocol = request.scheme protocol = request.scheme

View File

@ -1,6 +1,7 @@
from django.db import transaction from django.db import transaction
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from django.contrib.auth.hashers import make_password
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.decorators import action from rest_framework.decorators import action
@ -60,24 +61,10 @@ class UsuarioViewSet(viewsets.ModelViewSet):
) )
usuario.save() usuario.save()
# logging.error(f'clave = {input["clave"]}')
if input['clave']: if input['clave']:
logging.error('Modificar clave de usuario') logging.error('Modificar clave de usuario')
clave = models.UsuarioClave.objects.filter(login = usuario.login).first() usuario.clave = make_password(input['clave'])
if clave: usuario.save()
logging.error('Clave Usuario ya existe')
clave.clave_anterior = clave.clave
clave.clave = input['clave']
clave.fecha_modificacion = datetime.datetime.now()
clave.save()
else:
logging.error('Clave Usuario se creará')
clave = models.UsuarioClave(
login = usuario,
clave = input['clave'],
fecha_modificacion = datetime.datetime.now()
)
clave.save()
return Response({ return Response({
'rut': persona.rut, 'rut': persona.rut,
@ -101,13 +88,11 @@ class UsuarioViewSet(viewsets.ModelViewSet):
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
input = json.loads(request.body) input = json.loads(request.body)
logging.error(input)
try: try:
pk = input['rut'] pk = input['rut']
with transaction.atomic(): with transaction.atomic():
# validaciones se realiza a nivel del model # validaciones se realiza a nivel del model
persona = models.Persona.objects.filter(rut = pk).first() persona = models.Persona.objects.filter(rut = pk).first()
rol = models.Rol.objects.filter(id_rol = input.get('id_rol')).first() rol = models.Rol.objects.filter(id_rol = input.get('id_rol')).first()
@ -119,23 +104,8 @@ class UsuarioViewSet(viewsets.ModelViewSet):
if 'clave' in input: if 'clave' in input:
logging.error('Modificar clave de usuario') logging.error('Modificar clave de usuario')
logging.error(f'clave = {input["clave"]}') usuario.clave = make_password(input['clave'])
usuario.save()
clave = models.UsuarioClave.objects.filter(login = usuario.login).first()
if clave:
logging.error('Clave Usuario ya existe')
clave.clave_anterior = clave.clave
clave.clave = input['clave']
clave.fecha_modificacion = datetime.datetime.now()
clave.save()
else:
logging.error('Clave Usuario se creará')
clave = models.UsuarioClave(
login = usuario,
clave = input['clave'],
fecha_modificacion = datetime.datetime.now()
)
clave.save()
return Response({ return Response({
'rut': persona.rut, 'rut': persona.rut,

View File

@ -9,9 +9,12 @@ https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/ https://docs.djangoproject.com/en/4.2/ref/settings/
""" """
from dotenv import load_dotenv
from pathlib import Path from pathlib import Path
from decouple import config from decouple import config
import os
load_dotenv()
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
@ -62,7 +65,10 @@ ROOT_URLCONF = 'project.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [ BASE_DIR / 'dist' ], 'DIRS': [
os.path.join(BASE_DIR, 'dist'),
os.path.join(BASE_DIR, 'api', 'templates')
],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
@ -170,3 +176,10 @@ LOGGING = {
}, },
} }
""" """
EMAIL_HOST = config('SMTP_HOST')
EMAIL_PORT = config('SMTP_PORT', 587)
EMAIL_HOST_USER = config('SMTP_USER', 'tu_correo@gmail.com') # Tu dirección de correo
EMAIL_HOST_PASSWORD = config('SMTP_PASS', 'tu_contraseña') # Tu contraseña de correo
EMAIL_USE_TLS = config('SMTP_PROTOCOL') == 'tls'

View File

@ -4,6 +4,7 @@ djangorestframework
django-cors-headers django-cors-headers
django-filter django-filter
coreapi coreapi
python-dotenv
python-decouple python-decouple
PyJWT PyJWT
pymongo pymongo