diff --git a/sapl/base/forms.py b/sapl/base/forms.py index 092206429..754cca79e 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -40,7 +40,7 @@ from sapl.utils import (autor_label, autor_modal, ChoiceWithoutValidationField, FilterOverridesMetaMixin, FileFieldCheckMixin, ImageThumbnailFileInput, qs_override_django_filter, RANGE_ANOS, YES_NO_CHOICES, choice_tipos_normas, - GoogleRecapthaMixin, parlamentares_ativos, RANGE_MESES) + GoogleRecapthaMixin, parlamentares_ativos, RANGE_MESES, is_weak_password) from .models import AppConfig, CasaLegislativa @@ -288,8 +288,13 @@ class UserAdminForm(ModelForm): ) else: if new_password1 and new_password2: + if is_weak_password(new_password1): + raise forms.ValidationError(_( + 'A senha deve ter pelo menos 8 caracteres e incluir uma combinação ' + 'de letras maiúsculas e minúsculas, números e caracteres especiais.' + )) password_validation.validate_password( - new_password2, self.instance) + new_password1, self.instance) parlamentar = data.get('parlamentar', None) if parlamentar and parlamentar.votante_set.exists() and \ @@ -926,12 +931,12 @@ class CasaLegislativaForm(FileFieldCheckMixin, ModelForm): class LoginForm(AuthenticationForm): username = forms.CharField( - label="Username", max_length=30, + label="Usuário", max_length=30, widget=forms.TextInput( attrs={ 'class': 'form-control', 'name': 'username'})) password = forms.CharField( - label="Password", max_length=30, + label="Senha", max_length=30, widget=forms.PasswordInput( attrs={ 'class': 'form-control', 'name': 'password'})) @@ -1139,12 +1144,15 @@ class AlterarSenhaForm(Form): # TODO: caracteres alfanuméricos, maiúsculas (?), # TODO: senha atual igual a senha anterior, etc - if len(new_password1) < 6: + if is_weak_password(new_password1): self.logger.warning( - 'A senha informada não tem o mínimo de 6 caracteres.' + 'A senha deve ter pelo menos 8 caracteres e incluir uma combinação ' + 'de letras maiúsculas e minúsculas, números e caracteres especiais.' ) raise ValidationError( - "A senha informada deve ter no mínimo 6 caracteres") + 'A senha deve ter pelo menos 8 caracteres e incluir uma combinação ' + 'de letras maiúsculas e minúsculas, números e caracteres especiais.' + ) username = data['username'] old_password = data['old_password'] diff --git a/sapl/base/views.py b/sapl/base/views.py index 8417c0021..ce3a0a717 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -6,7 +6,7 @@ import os from django.apps.registry import apps from django.contrib import messages -from django.contrib.auth import get_user_model, views +from django.contrib.auth import authenticate, login, get_user_model, views from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.models import Group from django.contrib.auth.tokens import default_token_generator @@ -51,7 +51,7 @@ from sapl.sessao.models import (Bancada, SessaoPlenaria) from sapl.settings import EMAIL_SEND_USER from sapl.utils import (gerar_hash_arquivo, intervalos_tem_intersecao, mail_service_configured, SEPARADOR_HASH_PROPOSICAO, show_results_filter_set, google_recaptcha_configured, - get_client_ip, sapn_is_enabled) + get_client_ip, sapn_is_enabled, is_weak_password) from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, EstatisticasAcessoNormasForm) from .models import AppConfig, CasaLegislativa @@ -75,6 +75,21 @@ class LoginSapl(views.LoginView): template_name = 'base/login.html' authentication_form = LoginForm + def form_valid(self, form): + """Override do comportamento padrão para verificar senha fraca""" + username = form.cleaned_data.get('username') + password = form.cleaned_data.get('password') + + user = authenticate(self.request, username=username, password=password) + if user is not None: + login(self.request, user) + if is_weak_password(password): + self.request.session['weak_password'] = True + return redirect(self.get_success_url()) + + # Fallback se falhar a autenticação (tecnicamente não devia chegar aqui) + return super().form_invalid(form) + class ConfirmarEmailView(TemplateView): template_name = "email/confirma.html" @@ -1481,6 +1496,8 @@ class AlterarSenha(FormView): user.set_password(new_password) user.save() + self.request.session.pop('weak_password', None) + return super().form_valid(form) diff --git a/sapl/middleware.py b/sapl/middleware.py new file mode 100644 index 000000000..7a8d4ff65 --- /dev/null +++ b/sapl/middleware.py @@ -0,0 +1,21 @@ +import logging + +from django.shortcuts import redirect +from django.urls import reverse + + +class CheckWeakPasswordMiddleware: + logger = logging.getLogger(__name__) + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if request.user.is_authenticated and \ + request.session.get('weak_password', False) and \ + request.path != reverse('sapl.base:alterar_senha') and \ + request.path != reverse('sapl.base:logout'): + logging.warning(f"Usuário {request.user.username} possui senha fraca.") + return redirect('sapl.base:alterar_senha') + + return self.get_response(request) diff --git a/sapl/settings.py b/sapl/settings.py index 462cb8de5..956cf3138 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -141,6 +141,7 @@ MIDDLEWARE = [ 'whitenoise.middleware.WhiteNoiseMiddleware', 'django_prometheus.middleware.PrometheusAfterMiddleware', 'waffle.middleware.WaffleMiddleware', + 'sapl.middleware.CheckWeakPasswordMiddleware', ] if DEBUG: INSTALLED_APPS += ('debug_toolbar',) diff --git a/sapl/templates/base/alterar_senha.html b/sapl/templates/base/alterar_senha.html index 20956a763..6a824628f 100644 --- a/sapl/templates/base/alterar_senha.html +++ b/sapl/templates/base/alterar_senha.html @@ -3,10 +3,65 @@ {% load crispy_forms_tags %} {% block actions %}{% endblock %} {% block detail_content %} +
+ + + + {%if request.session.weak_password %} +