diff --git a/sloth/actions/__init__.py b/sloth/actions/__init__.py index f3e91fba..7a6172dd 100644 --- a/sloth/actions/__init__.py +++ b/sloth/actions/__init__.py @@ -615,6 +615,12 @@ class PasswordForm(Action): class Meta: verbose_name = 'Alterar Senha' + def __init__(self, *args, **kwargs): + self.user = kwargs.pop('user', None) + super().__init__(*args, **kwargs) + if not self.user: + self.user = self.request.user + def clean(self): password = self.cleaned_data.get('password') password2 = self.cleaned_data.get('password2') @@ -622,14 +628,14 @@ def clean(self): raise forms.ValidationError('Senhas não conferem.') if settings.SLOTH.get('FORCE_PASSWORD_DEFINITION') == True and settings.SLOTH.get('DEFAULT_PASSWORD'): - default_password = settings.SLOTH['DEFAULT_PASSWORD'](self.request.user) - if self.request.user.check_password(default_password) and self.request.user.check_password(password): + default_password = settings.SLOTH['DEFAULT_PASSWORD'](self.user) + if self.user.check_password(default_password) and self.user.check_password(password): raise forms.ValidationError('Senha não pode ser a senha padrão.') return self.cleaned_data def submit(self): - self.request.user.set_password(self.cleaned_data.get('password')) - self.request.user.save() - auth.login(self.request, self.request.user, backend='django.contrib.auth.backends.ModelBackend') + self.user.set_password(self.cleaned_data.get('password')) + self.user.save() + auth.login(self.request, self.user, backend='django.contrib.auth.backends.ModelBackend') self.redirect(message='Senha alterada com sucesso.') diff --git a/sloth/api/models.py b/sloth/api/models.py index b136e490..1d97a76d 100644 --- a/sloth/api/models.py +++ b/sloth/api/models.py @@ -42,7 +42,7 @@ def get_general_info(self): return self.values(('first_name', 'last_name'), 'username', 'email').verbose_name('Dados Gerais') def get_access_info(self): - return self.values('is_superuser',).verbose_name('Dados de Acesso') + return self.values(('is_superuser', 'is_active')).verbose_name('Dados de Acesso') @meta('Papéis') def get_roles(self): diff --git a/sloth/api/tokes.py b/sloth/api/tokes.py new file mode 100644 index 00000000..3a847f1f --- /dev/null +++ b/sloth/api/tokes.py @@ -0,0 +1,20 @@ +import six +from django.contrib.auth.tokens import PasswordResetTokenGenerator + + +class AccountActivationTokenGenerator(PasswordResetTokenGenerator): + def _make_hash_value(self, user, timestamp): + return ( + six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active) + ) + + +class PasswordResetToken(PasswordResetTokenGenerator): + def _make_hash_value(self, user, timestamp): + return ( + six.text_type(user.pk) + six.text_type(timestamp) + ) + + +account_activation_token = AccountActivationTokenGenerator() +password_reset_token = PasswordResetToken() diff --git a/sloth/app/urls.py b/sloth/app/urls.py index edc5779e..a2a92e0e 100644 --- a/sloth/app/urls.py +++ b/sloth/app/urls.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from django.urls import path, re_path -from . import views +from . import views urlpatterns = [ path('', views.index), @@ -15,6 +15,7 @@ path('app/logout/', views.logout), path('app/password/', views.password), path('app/action//', views.action), + path('app/account_activate///', views.account_activate), path('app///', views.dispatcher), path('app////', views.dispatcher), path('app////////', views.dispatcher), diff --git a/sloth/app/views.py b/sloth/app/views.py index 6daacdfa..61798f68 100644 --- a/sloth/app/views.py +++ b/sloth/app/views.py @@ -5,9 +5,13 @@ from django.conf import settings from django.contrib import auth, messages from django.core.exceptions import PermissionDenied +from django.core.mail import send_mail from django.http import JsonResponse, HttpResponseForbidden, HttpResponse, HttpResponseRedirect -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404 from django.template.loader import render_to_string +from django.utils.encoding import force_str +from django.utils.http import urlsafe_base64_decode +from sloth.api.tokes import account_activation_token from .templatetags.tags import is_ajax from ..core import views @@ -176,6 +180,23 @@ def logout(request): return HttpResponseRedirect('/') +def account_activate(request, uid64, token): + uid = force_str(urlsafe_base64_decode(uid64)) + user = get_object_or_404(User, pk=uid) + if user is not None and account_activation_token.check_token(user, token): + form = PasswordForm(request=request, user=user) + if form.is_valid(): + form.submit() + user.is_active = True + user.save() + auth.login(request, user) + return HttpResponseRedirect('/app/') + return render(request, ['app/default.html'], context(request, form=form)) + + messages.warning(request, "Link de ativação inválido") + return HttpResponseRedirect('/') + + def index(request): return HttpResponseRedirect('/app/') diff --git a/sloth/conf/settings.py b/sloth/conf/settings.py index cf0aa21d..3ccb80e8 100644 --- a/sloth/conf/settings.py +++ b/sloth/conf/settings.py @@ -45,7 +45,7 @@ 'ROLES':{ 'ALLOW_MULTIPLE': True }, - 'OAUTH_LOGIN': { + 'OAUTH_LOGIN': { 'APP': { 'TEXT': 'Acessar com APP', 'LOGO': None, @@ -60,6 +60,8 @@ 'LIST_PER_PAGE': 20, 'DEFAULT_PASSWORD': lambda user=None: '123', 'FORCE_PASSWORD_DEFINITION': False, + 'ADD_USER_CONFIRMATION': False, + 'LIST_PER_PAGE': 20, 'ICONS': ['fontawesome', 'materialicons'] } diff --git a/sloth/core/base.py b/sloth/core/base.py index 2b710d55..775aeff2 100644 --- a/sloth/core/base.py +++ b/sloth/core/base.py @@ -1,14 +1,20 @@ -from functools import lru_cache import types +from functools import lru_cache + from django.apps import apps from django.conf import settings from django.core.exceptions import FieldDoesNotExist +from django.core.mail import send_mail +from django.db import transaction from django.template.loader import render_to_string +from django.utils.encoding import force_bytes +from django.utils.http import urlsafe_base64_encode from sloth.actions import Action, ACTIONS -from sloth.core.valueset import ValueSet +from sloth.api.tokes import account_activation_token from sloth.core.queryset import QuerySet -from sloth.utils import to_snake_case, to_camel_case, getattrr +from sloth.core.valueset import ValueSet +from sloth.utils import to_snake_case, getattrr FILTER_FIELD_TYPES = 'BooleanField', 'NullBooleanField', 'ForeignKey', 'ForeignKeyPlus', 'DateField', 'DateFieldPlus' SEARCH_FIELD_TYPES = 'CharField', 'CharFieldPlus', 'TextField' @@ -145,6 +151,7 @@ def get_role_tuples(self, ignore_active_condition=False): # print('\n\n') return tuples + @transaction.atomic() def sync_roles(self, role_tuples): from django.contrib.auth.models import User user_id = None @@ -162,8 +169,31 @@ def sync_roles(self, role_tuples): default_password = settings.SLOTH['DEFAULT_PASSWORD'](user) else: default_password = '123' if settings.DEBUG else str(abs(hash(username))) + if settings.SLOTH.get('ADD_USER_CONFIRMATION') == True: + user.is_active = False user.set_password(default_password) user.save() + + site = settings.CSRF_TRUSTED_ORIGINS[0] + uid = urlsafe_base64_encode(force_bytes(user.pk)) + token = account_activation_token.make_token(user) + send_mail( + subject="Cadastro no RAIZ", + message=None, + html_message=f""" +

+ Olá {user}, +

+ Para ativar o seu cadastro e definir sua senha clique no link abaixo: +

+ Definir senha +

+ Se o link acima não funcionar, por favor, copie e cole a URL no seu navegador. + """, + from_email=settings.DEFAULT_FROM_EMAIL, + recipient_list=(email,), + ) + user_id = user.id role.objects.get_or_create( user_id=user_id, name=scope_name,