-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cce8ab4
commit fab18d0
Showing
40 changed files
with
1,321 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from django.contrib import admin # noqa | ||
from django.utils.translation import gettext_lazy as _ # noqa | ||
|
||
# Register your models here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from django.apps import AppConfig | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
|
||
class RestauthConfig(AppConfig): | ||
name = 'apps.restauth' | ||
verbose_name = _("Restauths") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
from django.contrib.auth.forms import PasswordResetForm as DjangoPasswordResetForm | ||
from django.contrib.auth.tokens import default_token_generator | ||
from django.contrib.sites.shortcuts import get_current_site | ||
from django.utils.encoding import force_bytes | ||
from django.utils.http import urlsafe_base64_encode | ||
|
||
from apps.user.models import User | ||
|
||
|
||
class PasswordResetForm(DjangoPasswordResetForm): | ||
def get_users(self, email): | ||
return User.objects.filter(email=email, is_active=True) | ||
|
||
def save(self, domain_override=None, site_name_override=None, | ||
subject_template_name='restauth/password_reset_subject.txt', | ||
email_template_name='restauth/password_reset_email.html', | ||
use_https=False, token_generator=default_token_generator, | ||
from_email=None, request=None, html_email_template_name=None, | ||
extra_email_context=None): | ||
""" | ||
Generate a one-use only link for resetting password and send it to the | ||
user. | ||
""" | ||
email = self.cleaned_data["email"] | ||
for user in self.get_users(email): | ||
if not domain_override: | ||
current_site = get_current_site(request) | ||
domain = current_site.domain | ||
site_name = current_site.name | ||
else: | ||
# Overwrite django's default to allow site_name override | ||
domain = domain_override | ||
site_name = site_name_override or domain | ||
user_email = user.email | ||
context = { | ||
'email': user_email, | ||
'domain': domain, | ||
'site_name': site_name, | ||
'uid': urlsafe_base64_encode(force_bytes(user.pk)), | ||
'user': user, | ||
'token': token_generator.make_token(user), | ||
'protocol': 'https' if use_https else 'http', | ||
**(extra_email_context or {}), | ||
} | ||
self.send_mail( | ||
subject_template_name, email_template_name, context, from_email, | ||
user_email, html_email_template_name=html_email_template_name, | ||
) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from django.db import models # noqa | ||
from django.utils.translation import gettext_lazy as _ # noqa | ||
|
||
# Create your models here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
from rest_framework import exceptions, serializers | ||
from dj_rest_auth import serializers as dj_rest_auth_serializers | ||
from dj_rest_auth.registration import serializers as dj_rest_auth_registration_serializers | ||
from rest_framework.validators import UniqueValidator | ||
|
||
from django.db.transaction import atomic | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
from apps.user.models import ExecutiveUser, User | ||
from shared.fields import DocumentField, PhoneNumberField | ||
from shared.validators import NamedBRCPFValidator | ||
from .forms import PasswordResetForm | ||
|
||
DUPLICATE_DOCUMENT = _("Já existe um usuário cadastrado com este documento.") | ||
DOCUMENT = 'document' | ||
|
||
|
||
class PasswordResetSerializer(dj_rest_auth_serializers.PasswordResetSerializer): | ||
password_reset_form_class = PasswordResetForm | ||
|
||
|
||
class LoginSerializer(dj_rest_auth_serializers.LoginSerializer): | ||
document = serializers.CharField(required=False) | ||
|
||
def _validate_document(self, document, password): | ||
user = None | ||
|
||
if document and password: | ||
user = self.authenticate(document=document, password=password) | ||
else: | ||
msg = _('Must include "document" and "password".') | ||
raise exceptions.ValidationError(msg) | ||
|
||
return user | ||
|
||
def get_auth_user(self, document, email, username, password): | ||
from allauth.account import app_settings | ||
|
||
if app_settings.AUTHENTICATION_METHOD == DOCUMENT: | ||
return self._validate_document(document, password) | ||
|
||
return super().get_auth_user(username, email, password) | ||
|
||
def validate(self, attrs): | ||
document = attrs.get('document') | ||
password = attrs.get('password') | ||
email = attrs.get('email') | ||
username = attrs.get('username') | ||
user = self.get_auth_user(document, email, username, password) | ||
|
||
if not user: | ||
msg = _('Unable to log in with provided credentials.') | ||
raise exceptions.ValidationError(msg) | ||
|
||
self.validate_auth_user_status(user) | ||
|
||
attrs['user'] = user | ||
return attrs | ||
|
||
|
||
class RegisterSerializer(dj_rest_auth_registration_serializers.RegisterSerializer): | ||
"""Serializer to user self-register (only civil society members).""" | ||
name = serializers.CharField() | ||
email = serializers.EmailField() | ||
is_brazilian = serializers.BooleanField() | ||
document = DocumentField( | ||
validators=[UniqueValidator(queryset=User.objects.all(), message=DUPLICATE_DOCUMENT)] | ||
) | ||
cellphone = PhoneNumberField() | ||
|
||
def get_cleaned_data(self): | ||
return { | ||
'email': self.validated_data.get('email', ''), | ||
'password': self.validated_data.get('password1', ''), | ||
'name': self.validated_data['name'], | ||
'is_brazilian': self.validated_data['is_brazilian'], | ||
'document': self.validated_data['document'], | ||
'cellphone': self.validated_data['cellphone'], | ||
} | ||
|
||
def validate(self, attrs): | ||
if attrs['is_brazilian']: | ||
validator = NamedBRCPFValidator('document') | ||
validator(attrs['document']) | ||
return attrs | ||
|
||
@atomic | ||
def save(self, request): | ||
user = ExecutiveUser.objects.create_user(**self.get_cleaned_data()) | ||
|
||
return user |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import pytest # noqa | ||
|
||
# Create your tests here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import pytest # noqa | ||
|
||
# Create your tests here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import pytest # noqa | ||
|
||
# Create your tests here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from django.urls import include, path | ||
|
||
from . import views | ||
|
||
app_name = 'auth' | ||
|
||
# The API URLs are now determined automatically by the router. | ||
urlpatterns = [ | ||
path('login/', views.LoginView.as_view(), name='rest_login'), | ||
path('password/reset/', views.PasswordResetView.as_view(), name='rest_password_reset'), | ||
path('password/reset/confirm/', views.PasswordResetConfirmView.as_view(), | ||
name='rest_password_reset_confirm'), | ||
# URLs that require a user to be logged in with a valid session / token. | ||
path('logout/', views.LogoutView.as_view(), name='rest_logout'), | ||
path('user/', views.UserDetailsView.as_view(), name='rest_user_details'), | ||
path('user/delete/', views.UserDestroyView.as_view(), name='rest_user_destroy'), | ||
path('password/change/', views.PasswordChangeView.as_view(), name='rest_password_change'), | ||
path('registration/', include('dj_rest_auth.registration.urls')), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
from dj_rest_auth import views as dj_rest_auth_views | ||
from rest_framework.generics import DestroyAPIView | ||
from apps.user.serializers import BaseUserSerializer, ExecutiveUserSerializer, TechnicalUserSerializer | ||
|
||
from shared.authentication import BearerAuthentication | ||
|
||
|
||
class LoginView(dj_rest_auth_views.LoginView): | ||
# Prevents clash between session and token authentication | ||
authentication_classes = (BearerAuthentication,) | ||
|
||
|
||
class PasswordResetView(dj_rest_auth_views.PasswordResetView): | ||
authentication_classes = (BearerAuthentication,) | ||
|
||
|
||
class PasswordResetConfirmView(dj_rest_auth_views.PasswordResetConfirmView): | ||
authentication_classes = (BearerAuthentication,) | ||
|
||
|
||
class LogoutView(dj_rest_auth_views.LogoutView): | ||
pass | ||
|
||
|
||
class UserDetailsView(dj_rest_auth_views.UserDetailsView): | ||
def get_serializer_class(self): | ||
user = self.get_object() | ||
if user.is_civil: | ||
return ExecutiveUserSerializer | ||
elif user.is_technical: | ||
return TechnicalUserSerializer | ||
else: | ||
return BaseUserSerializer | ||
|
||
|
||
class UserDestroyView(DestroyAPIView): | ||
"""Destroy (delete) logged in user.""" | ||
def get_object(self): | ||
return self.request.user | ||
|
||
|
||
class PasswordChangeView(dj_rest_auth_views.PasswordChangeView): | ||
pass |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from django.contrib import admin # noqa | ||
from django.utils.translation import gettext_lazy as _ # noqa | ||
|
||
# Register your models here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from django.apps import AppConfig | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
|
||
class UserConfig(AppConfig): | ||
name = 'apps.user' | ||
verbose_name = _("Users") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
from django.contrib.auth.models import BaseUserManager | ||
from django.utils.translation import gettext_lazy as _ # noqa | ||
|
||
|
||
class UserManager(BaseUserManager): | ||
use_in_migrations = True | ||
|
||
def _create_user(self, email, password, **extra_fields): | ||
""" | ||
Create and save a user with the given email and password. | ||
""" | ||
if not email: | ||
raise ValueError('The given email must be set') | ||
email = self.normalize_email(email) | ||
user = self.model(email=email, **extra_fields) | ||
user.set_password(password) | ||
user.save(using=self._db) | ||
return user | ||
|
||
def create_user(self, email, password=None, **extra_fields): | ||
extra_fields.setdefault('is_staff', False) | ||
extra_fields.setdefault('is_superuser', False) | ||
return self._create_user(email, password, **extra_fields) | ||
|
||
def create_superuser(self, email, password, **extra_fields): | ||
extra_fields.setdefault('is_staff', True) | ||
extra_fields.setdefault('is_superuser', True) | ||
|
||
if extra_fields.get('is_staff') is not True: | ||
raise ValueError("Superuser must have is_staff=True.") | ||
if extra_fields.get('is_superuser') is not True: | ||
raise ValueError("Superuser must have is_superuser=True.") | ||
|
||
return self._create_user(email, password, **extra_fields) | ||
|
||
|
||
class ExecutiveUserManager(UserManager): | ||
def _create_user(self, email, password, **extra_fields): | ||
extra_fields.setdefault('type', self.model.Type.EXECUTIVE) | ||
|
||
if extra_fields.get('type') != self.model.Type.EXECUTIVE: | ||
raise ValueError("Executive User must have type='Executive'.") | ||
|
||
return super()._create_user(email, password, **extra_fields) | ||
|
||
def get_queryset(self): | ||
return super().get_queryset().filter(type=self.model.Type.EXECUTIVE) | ||
|
||
|
||
class TechnicalUserManager(UserManager): | ||
def _create_user(self, email, password, **extra_fields): | ||
extra_fields.setdefault('type', self.model.Type.TECHNICAL) | ||
|
||
if extra_fields.get('type') != self.model.Type.TECHNICAL: | ||
raise ValueError("Technical User must have type='technical'.") | ||
|
||
return super()._create_user(email, password, **extra_fields) | ||
|
||
def get_queryset(self): | ||
return super().get_queryset().filter(type=self.model.Type.TECHNICAL) | ||
|
||
|
||
class AcademicUserManager(UserManager): | ||
def _create_user(self, email, password, **extra_fields): | ||
extra_fields.setdefault('type', self.model.Type.ACADEMIC) | ||
|
||
if extra_fields.get('type') != self.model.Type.ACADEMIC: | ||
raise ValueError("Academic User must have type='academic'.") | ||
|
||
return super()._create_user(email, password, **extra_fields) | ||
|
||
def get_queryset(self): | ||
return super().get_queryset().filter(type=self.model.Type.ACADEMIC) |
Oops, something went wrong.