Skip to content

Commit e8c2ec1

Browse files
release: v0.0.1a14.dev20240302 (#38)
* Tests: Valida more views Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test: Validate more tests Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test: validate more views Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * docs: Update docs Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test: Update test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> --------- Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Remove setup.py file Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * chore: Update copyrithg notice Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Remove black from test requeriments Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Update dev dependencies Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: cleanup Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: update requeriments Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Update build metadata Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * docs: update config Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: update docker base image Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Update coverga report Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * buil: clean Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: update atributes Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Update setup Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Test coverage update Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * test: More tests Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * release: v0.0.1a14.dev20240223 Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Update config Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * feat: Configure mail backend with Flask-Mailman (#39) * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * docs: Update docs Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test: Update test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> --------- Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * release: v0.0.1a14.dev20240223 (#35) * Tests: Valida more views Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test: Validate more tests Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test: validate more views Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Update password hasher to argon2 (#37) * feat: Replace bcryp with argon2 for password hash References: - https://passlib.readthedocs.io/en/stable/narr/quickstart.html#recommended-hashes - https://pypi.org/project/argon2-cffi/ - https://pypi.org/project/bcrypt/ Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * docs: Update docs Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * Test: Update test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> --------- Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Remove setup.py file Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * chore: Update copyrithg notice Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Remove black from test requeriments Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Update dev dependencies Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: cleanup Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: update requeriments Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Update build metadata Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * docs: update config Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: update docker base image Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Update coverga report Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * buil: clean Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: update atributes Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Update setup Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Test coverage update Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * test: More tests Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * release: v0.0.1a14.dev20240223 Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> --------- Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Use flask-mailman Signed-off-by: William Moreno Reyes <williamjmorenor@fedoraproject.org> * feat: Setup email backend with Flask-Mailman Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> --------- Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> Signed-off-by: William Moreno Reyes <williamjmorenor@fedoraproject.org> Signed-off-by: William Moreno <williamjmorenor@gmail.com> * build: Update config Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: update Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Update config Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Update entrypoint Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * test: Update test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Clean Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * build: Update test Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> * release: v0.0.1a14.dev20240302 Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> --------- Signed-off-by: William José Moreno Reyes <williamjmorenor@gmail.com> Signed-off-by: William Moreno Reyes <williamjmorenor@fedoraproject.org> Signed-off-by: William Moreno <williamjmorenor@gmail.com>
1 parent fdd8a04 commit e8c2ec1

20 files changed

+437
-298
lines changed

.coveragerc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ source = now_lms
33

44
[report]
55
exclude_lines =
6-
pragma: no cover
76
def __repr__
87
if self.debug:
98
if settings.DEBUG

.do/deploy.template.yaml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
spec:
22
name: nowlearningmanagementsystem
33
services:
4-
- name: web
5-
build_command: python -m flask setup
6-
run_command: python -m flask serve
7-
git:
8-
branch: main
9-
repo_clone_url: https://github.com/bmosoluciones/now-lms.git
10-
envs:
11-
- key: LMS_KEY
12-
value: "nsjksldknsdlkdsljdnsdjñasññqldñaas554dlkallas"
13-
type: SECRET
14-
- key: LMS_USER
15-
value: "lms-admin"
16-
- key: LMS_PSWD
17-
value: "lms-admin"
18-
type: SECRET
4+
- name: web
5+
build_command: python -m flask setup
6+
run_command: python -m flask serve
7+
git:
8+
branch: main
9+
repo_clone_url: https://github.com/bmosoluciones/now-lms.git
10+
envs:
11+
- key: LMS_KEY
12+
value: "nsjksldknsdlkdsljdnsdjñasññqldñaas554dlkallaskkkkllk"
13+
type: SECRET
14+
- key: LMS_USER
15+
value: "lms-admin"
16+
- key: LMS_PSWD
17+
value: "lms-admin"
18+
type: SECRET
1919
databases:
20-
- name: example-db
20+
- name: example-db

.dockerignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,4 @@ passenger_wsgi.py
4646
wsgi.py
4747
test.txt
4848
now_lms.egg-info
49-
5049
.ruff_cache

.editorconfig

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@ charset = utf-8
1010
max_line_length = 127
1111

1212
[*.{yml,yaml,json,js,css,html}]
13-
indent_size = 2
13+
indent_size = 2
14+
15+
[{package.json,.travis.yml}]
16+
indent_style = space
17+
indent_size = 2

LICENSE

Lines changed: 183 additions & 183 deletions
Large diffs are not rendered by default.

Procfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
web: python -m now_lms
1+
# Main proccess
2+
web: python -m now_lms

development.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
-r test.txt
22

3-
flask-debugtoolbar
4-
flask_profiler
5-
63
# Servidor de desarrollo
74
hupper
85

docker-entry-point.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/sh
22
set -e
33

4-
/usr/bin/python3.9 -m flask db upgrade
4+
# /usr/bin/python3.9 -m flask db upgrade
55
/usr/bin/python3.9 -m now_lms

docs/setup-conf.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ Note that initial log messages will refer to the default options because you are
7777

7878
You can use the following options to configure NOW-LMS:
7979

80-
- **SECRET_KEY** (<span style="color:red">required</span>): A secure string used to secure the login proccess and form validation.
80+
- **SECRET_KEY** (<span style="color:red">required</span>): A secure string used to secure the login proccess and form validation and to hash sensible data stored in the system database, if this parameter changes, hashed secrets will not be
81+
decripted and you will need to save then againg.
8182
- **SQLALCHEMY_DATABASE_URI** (<span style="color:red">required</span>): A valid SQLAlchemy conextion string, SQLite, MySQL version
8283
8 and a resent version of PostgreSQL must work out of the box, MariaDB ans MS SQLServer should work but we not test the release versus this database engines. Checkout the
8384
[SQLAlchemy docs](https://docs.sqlalchemy.org/en/20/core/engines.html) to valid examples to conections strings, the PyMSQL and PG800 database drivers are installed as normal dependencies, other database engines may requiere manual drivers setup.

now_lms/__init__.py

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
from flask.cli import FlaskGroup
4848
from flask_alembic import Alembic
4949
from flask_login import LoginManager, current_user
50-
from flask_mail import Mail
50+
from flask_mailman import Mail
5151
from flask_mde import Mde
5252
from flask_uploads import configure_uploads
5353
from pg8000.dbapi import ProgrammingError as PGProgrammingError
@@ -58,6 +58,7 @@
5858
# ---------------------------------------------------------------------------------------
5959
# Recursos locales
6060
# ---------------------------------------------------------------------------------------
61+
from now_lms.auth import descifrar_secreto
6162
from now_lms.cache import cache
6263
from now_lms.config import (
6364
CONFIGURACION,
@@ -136,6 +137,7 @@
136137
alembic: Alembic = Alembic()
137138
administrador_sesion: LoginManager = LoginManager()
138139
mde: Mde = Mde()
140+
mail: Mail = Mail()
139141

140142

141143
# ---------------------------------------------------------------------------------------
@@ -151,36 +153,7 @@ def inicializa_extenciones_terceros(flask_app: Flask):
151153
administrador_sesion.init_app(flask_app)
152154
cache.init_app(flask_app)
153155
mde.init_app(flask_app)
154-
if environ.get("PROFILER"): # pragma: no cover
155-
log.warning("Profiler activo, no se recomienda el uso de esta opción en entornos reales.")
156-
try:
157-
from flask_debugtoolbar import DebugToolbarExtension
158-
from flask_profiler import Profiler
159-
160-
app.debug = True
161-
162-
app.config["flask_profiler"] = {
163-
"enabled": app.config["DEBUG"],
164-
"storage": {"engine": "sqlite"},
165-
"basicAuth": {"enabled": True, "username": "admin", "password": "admin"},
166-
"ignore": ["^/static/.*"],
167-
}
168-
169-
app.config["DEBUG_TB_INTERCEPT_REDIRECTS"] = False
170-
toolbar = DebugToolbarExtension(app)
171-
profiler = Profiler(app)
172-
log.debug("Profiler activo")
173-
except ModuleNotFoundError: # pragma: no cover
174-
toolbar = None
175-
profiler = None
176-
except ImportError: # pragma: no cover
177-
toolbar = None
178-
profiler = None
179-
180-
if toolbar:
181-
log.info("Flask development toolbar enabled.")
182-
if profiler:
183-
log.info("Flask profiler enabled.")
156+
mail.init_app(flask_app)
184157
log.trace("Extensiones de terceros iniciadas correctamente.")
185158

186159

@@ -395,7 +368,7 @@ def initial_setup(with_examples=False, with_tests=False):
395368
log.info("NOW - LMS iniciado correctamente.")
396369

397370

398-
def init_app(with_examples=False): # pragma: no cover
371+
def init_app(with_examples=False):
399372
"""Funcion auxiliar para iniciar la aplicacion."""
400373

401374
with lms_app.app_context():
@@ -442,21 +415,26 @@ def init_app(with_examples=False): # pragma: no cover
442415
log.trace("Acceso a base de datos verificado.")
443416
config = Configuracion.query.first()
444417

445-
if config.email:
418+
if (
419+
config.email
420+
and config.MAIL_HOST is not None
421+
and config.MAIL_PORT is not None
422+
and config.MAIL_USERNAME is not None
423+
and config.MAIL_PASSWORD is not None
424+
):
425+
log.trace("Cargando configuración de correo electronico.")
446426
lms_app.config.update(
447427
{
448-
"MAIL_SERVER": config.mail_server,
449-
"MAIL_PORT": config.mail_port,
450-
"MAIL_USERNAME": config.mail_username,
451-
"MAIL_PASSWORD": config.mail_password,
452-
"MAIL_USE_TLS": config.mail_use_tls,
453-
"MAIL_USE_SSL": config.mail_use_ssl,
428+
"MAIL_HOST": config.MAIL_HOST,
429+
"MAIL_PORT": config.MAIL_PORT,
430+
"MAIL_USERNAME": config.MAIL_USERNAME,
431+
"MAIL_PASSWORD": descifrar_secreto(config.MAIL_PASSWORD),
432+
"MAIL_USE_TLS": config.MAIL_USE_TLS,
433+
"MAIL_USE_SSL": config.MAIL_USE_SSL,
454434
}
455435
)
456436
if DESARROLLO:
457-
lms_app.config.update({"MAIL_SUPPRESS_SEND": True})
458-
e_mail = Mail()
459-
e_mail.init_app(lms_app)
437+
lms_app.config.update({"MAIL_BACKEND": "dummy"})
460438

461439

462440
# ---------------------------------------------------------------------------------------

now_lms/auth.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
# ---------------------------------------------------------------------------------------
2222
# Libreria estandar
2323
# ---------------------------------------------------------------------------------------
24+
import base64
2425
from datetime import datetime
2526
from functools import wraps
2627

@@ -29,7 +30,10 @@
2930
# ---------------------------------------------------------------------------------------
3031
from argon2 import PasswordHasher
3132
from argon2.exceptions import VerifyMismatchError
32-
from flask import abort, flash
33+
from cryptography.fernet import Fernet
34+
from cryptography.hazmat.primitives import hashes
35+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
36+
from flask import abort, flash, current_app
3337
from flask_login import current_user
3438

3539
# ---------------------------------------------------------------------------------------
@@ -88,3 +92,53 @@ def wrapper(*args, **kwargs):
8892
return wrapper
8993

9094
return decorator_verifica_acceso
95+
96+
97+
def proteger_secreto(password):
98+
"""
99+
Devuelve el hash de una contraseña.
100+
101+
Se requiere que el parametro "SECRET_KEY" este establecido en la configuración de la aplicacion,
102+
si cambia el valor de este parametro debera actualizar la configuración ya se utiliza el mismo
103+
parametro para obtener la contraseña original.
104+
"""
105+
106+
with current_app.app_context():
107+
from now_lms.db import database, Configuracion
108+
109+
config = database.session.execute(database.select(Configuracion)).first()[0]
110+
111+
kdf = PBKDF2HMAC(
112+
algorithm=hashes.SHA256(),
113+
length=32,
114+
salt=config.r,
115+
iterations=480000,
116+
)
117+
key = base64.urlsafe_b64encode(kdf.derive(current_app.config.get("SECRET_KEY").encode()))
118+
f = Fernet(key)
119+
return f.encrypt(password.encode())
120+
121+
122+
def descifrar_secreto(hash):
123+
"""
124+
Devuelve el valor de una contraseña protegida.
125+
126+
Se utiliza el valor del parametro "SECRET_KEY" de la configuración de la aplicación para decodificar
127+
la contraseña original, si el parametro "SECRET_KEY" cambia en la configuración no se posible desifrar
128+
la contraseña original, debera generar una nueva.
129+
"""
130+
131+
with current_app.app_context():
132+
from now_lms.db import database, Configuracion
133+
134+
config = database.session.execute(database.select(Configuracion)).first()[0]
135+
136+
kdf = PBKDF2HMAC(
137+
algorithm=hashes.SHA256(),
138+
length=32,
139+
salt=config.r,
140+
iterations=480000,
141+
)
142+
key = base64.urlsafe_b64encode(kdf.derive(current_app.config.get("SECRET_KEY").encode()))
143+
f = Fernet(key)
144+
return f.decrypt(hash)

now_lms/db/__init__.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -351,12 +351,21 @@ class Configuracion(database.Model, BaseTabla):
351351
custom_logo = database.Column(database.Boolean())
352352
# Email settings
353353
email = database.Column(database.Boolean())
354-
mail_server = database.Column(database.String(50))
355-
mail_port = database.Column(database.String(50))
356-
mail_username = database.Column(database.String(50))
357-
mail_password = database.Column(database.String(50))
358-
mail_use_tls = database.Column(database.Boolean())
359-
mail_use_ssl = database.Column(database.Boolean())
354+
MAIL_HOST = database.Column(database.String(50))
355+
MAIL_PORT = database.Column(database.String(50))
356+
MAIL_USERNAME = database.Column(database.String(50))
357+
MAIL_PASSWORD = database.Column(database.LargeBinary())
358+
MAIL_USE_TLS = database.Column(database.Boolean())
359+
MAIL_USE_SSL = database.Column(database.Boolean())
360+
email_verificado = database.Column(database.Boolean())
361+
# These are ramdon bytes to protect passwords like SMTP mail password or others
362+
# than the users of the system will estore in the database as configuration parameters
363+
# those password are not goint to be saved in plain text, we will save them in hashed version
364+
# with your app SECRET_KEY
365+
# So, if any one have access to your database it will access to the hashed
366+
# version of the password and this 16 bytes, but will need your app SECRET_KEY
367+
# in order to decode those passwords.
368+
r = database.Column(database.LargeBinary())
360369

361370

362371
class Categoria(database.Model, BaseTabla):

now_lms/db/tools.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,20 @@ def verifica_estudiante_asignado_a_curso(id_curso: Union[None, str] = None):
8585

8686
def crear_configuracion_predeterminada():
8787
"""Crea configuración predeterminada de la aplicación."""
88+
89+
from os import urandom
90+
8891
config = Configuracion(
8992
titulo="NOW LMS",
9093
descripcion="Sistema de aprendizaje en linea.",
9194
modo="mooc",
9295
style="dark",
9396
custom_logo=False,
9497
email=False,
95-
mail_server=None,
96-
mail_port=None,
97-
mail_use_tls=False,
98-
mail_use_ssl=False,
99-
mail_username=None,
100-
mail_password=None,
98+
MAIL_USE_TLS=False,
99+
MAIL_USE_SSL=False,
101100
moneda="C$",
101+
r=urandom(16),
102102
)
103103
database.session.add(config)
104104
database.session.commit()

now_lms/forms/__init__.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,12 @@ class MailForm(FlaskForm):
107107
"""Formulario de configuración de correo electronico."""
108108

109109
email = BooleanField(validators=[])
110-
mail_server = StringField(validators=[DataRequired()])
111-
mail_port = StringField(validators=[DataRequired()])
112-
mail_use_tls = BooleanField(validators=[])
113-
mail_use_ssl = BooleanField(validators=[])
114-
mail_username = StringField(validators=[DataRequired()])
115-
mail_password = StringField(validators=[DataRequired()])
110+
MAIL_HOST = StringField(validators=[DataRequired()])
111+
MAIL_PORT = StringField(validators=[DataRequired()])
112+
MAIL_USERNAME = StringField(validators=[DataRequired()])
113+
MAIL_PASSWORD = PasswordField()
114+
MAIL_USE_TLS = BooleanField(validators=[])
115+
MAIL_USE_SSL = BooleanField(validators=[])
116116

117117

118118
class LogonForm(FlaskForm):

0 commit comments

Comments
 (0)