PK z:Jyt t plugs_auth/decorators.pyfrom plugs_auth.settings import plugs_auth_settings as settings def dynamicviewset(viewset): """ The activate route only makes sense if user activation is required, remove the route if activation is turned off """ if not settings['REQUIRE_ACTIVATION'] and hasattr(viewset, 'activate'): delattr(viewset, 'activate') return viewset PK z:Js S plugs_auth/views.py""" Plugs Auth Views """ from django.contrib.auth import get_user_model from django.shortcuts import get_object_or_404 from rest_framework import permissions from rest_framework.decorators import list_route from rest_framework.response import Response from plugs_core.viewsets import CreateUpdateReadViewSet from plugs_auth.serializers import ResetSerializer, SetSerializer from plugs_auth import utils from plugs_auth.decorators import dynamicviewset @dynamicviewset class PlugsUserViewSet(CreateUpdateReadViewSet): """ Use this class as a base class for a user viewset, this class provides activate, reset_password, set_password, resend_verification_email and populates the user object with the language using user submited data, accept-language header or project settings """ def perform_create(self, serializer): serializer.validated_data['language'] = utils.get_language_code(self.request, serializer) serializer.save() @list_route(methods=['GET'], permission_classes=[permissions.AllowAny]) def activate(self, request): kwargs = {'token': request.query_params.get('token'), 'is_active': False} user = get_object_or_404(get_user_model(), **kwargs) user.is_active = True user.save() user.send_account_activated_email() return Response(data={"message": "Activated"}) @list_route(methods=['POST'], permission_classes=[permissions.AllowAny]) def reset_password(self, request): """ Starts the reset password process by sending an email """ serializer = ResetSerializer(data=request.data) serializer.is_valid(raise_exception=True) user = get_object_or_404(get_user_model(), **serializer.data) user.set_token() user.send_reset_password_email() user.save() return Response(data={"message": "Email Sent"}) @list_route(methods=['POST'], permission_classes=[permissions.AllowAny]) def set_password(self, request): """ Sets a new password after a reset password request """ serializer = SetSerializer(data=request.data) serializer.is_valid(raise_exception=True) data = serializer.data password = data.pop('password') user = get_object_or_404(get_user_model(), **data) user.password = password user.is_active = True # in case the user was inactive user.set_token() user.save() return Response(data={"message": "New password set"}) @list_route(methods=['POST'], permission_classes=[permissions.AllowAny]) def resend_verification_email(self, request): """ Resends the verification email to a user """ kwargs = {'email': request.data.get('email'), 'is_active': False} user = get_object_or_404(get_user_model(), **kwargs) user.send_activation_email() return Response(data={"message": "Email sent"}) PK 6Iw?z z plugs_auth/utils.py""" Plugs Auth Utils """ from django.conf import settings def parse_accept_language(accept_header): """ Taken from: https://siongui.github.io/2012/10/11/python-parse-accept-language-in-http-request-header/ """ languages = accept_header.split(",") locale_q_pairs = [] for language in languages: if language.split(";")[0] == language: # no q => q = 1 locale_q_pairs.append((language.strip(), "1")) else: locale = language.split(";")[0].strip() q = language.split(";")[1].split("=")[1] locale_q_pairs.append((locale, q)) return locale_q_pairs def get_languages_list(): """ Get the list of project supported languages """ return [language[0] for language in settings.LANGUAGES] def is_supported_language(language): """ Given a language check if it belongs to the supported list """ return language in get_languages_list() def language_from_header(header): """ Split the accept language header and check if a language preference is available in the settings """ preferences = parse_accept_language(header) languages = get_languages_list() for preference in preferences: # iterate over the languages setting and return the first match if is_supported_language(preference[0]): return preference[0] def get_language_code(request, serializer): """ Check request and provided data to define the user language """ # first check if user provided a valid language_code language_code = serializer.validated_data.get('language') if is_supported_language('language_code'): return language_code # second check if is possible to determine the language from the # request headers try: accept_header = request.META['HTTP_ACCEPT_LANGUAGE'] language_code = language_from_header(accept_header) # third, set the user language using the settings except KeyError: # in case the Locale Middleware is not available, use the language # set in the settings, the Django default is en-US # more information on the format used in the settings # http://www.i18nguy.com/unicode/language-identifiers.html # for now we are only using the language part of the code language_code = settings.LANGUAGE_CODE.split('-')[0] return language_code PK UI &85 plugs_auth/serializers.py""" Custom authentication serializers """ from rest_framework import serializers class ResetSerializer(serializers.Serializer): """ Custom serializer to use with reset_password api view """ email = serializers.EmailField() class SetSerializer(serializers.Serializer): """ Custom serializer to use with set_password api view """ email = serializers.EmailField() password = serializers.CharField() token = serializers.CharField() PK z:J{M.F F plugs_auth/managers.py""" Plugs Authentication Managers """ from plugs_auth.settings import plugs_auth_settings as settings from django.contrib.auth.models import BaseUserManager class PlugsAuthManager(BaseUserManager): """ Custom manager, the parent class provides some utils to manage users, like normalize_email and make_random_password """ def _create_user(self, email, password, **extra_fields): """ Creates and saves a User with the given username, email and password. """ 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(self, email, password=None, silent=False, **extra_fields): if silent: print('Silent has been deprecated') extra_fields.setdefault('is_staff', False) extra_fields.setdefault('is_superuser', False) user = self._create_user(email, password, **extra_fields) if settings['REQUIRE_ACTIVATION']: user.send_activation_email() else: user.is_active=True return user def create_superuser(self, email, password, **extra_fields): """ This method is provided to enable createsuperuser command a superuser is automatically made staff and active """ extra_fields.setdefault('is_active', True) 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) PK 6I9 plugs_auth/models.py""" Plugs Base Auth Model """ from django.db import models from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin from plugs_core import utils from plugs_mail import utils as mail_utils from plugs_auth import emails from plugs_auth.managers import PlugsAuthManager class PlugsAuthModel(AbstractBaseUser, PermissionsMixin): """ Member model """ email = models.EmailField(unique=True) is_active = models.BooleanField(default=False) is_staff = models.BooleanField(default=False) token = models.CharField(max_length=24, null=False, unique=True) language = models.CharField(max_length=2, default='en') objects = PlugsAuthManager() # the field used to authenticate a user USERNAME_FIELD = 'email' def __init__(self, *args, **kwargs): """ Overring init to store original password """ super(PlugsAuthModel, self).__init__(*args, **kwargs) self.__original_password = self.password @property def username(self): """ The field used to authenticate a user """ return self.USERNAME_FIELD def set_token(self): """ Set a unique verified token """ params = {'length': 24} queryset = self.__class__.objects self.token = utils.get_db_distinct(queryset, 'token', utils.random_string, **params) def send_reset_password_email(self): """ Send email to user with reset password link """ mail_utils.to_email(emails.ResetPassword, self.email, self.language, **{'user': self}) def send_activation_email(self): """ Send email to user with activation details """ mail_utils.to_email(emails.ActivateAccount, self.email, self.language, **{'user': self}) def send_account_activated_email(self): """ Send email to user saying account has been activated """ mail_utils.to_email(emails.AccountActivated, self.email, self.language, **{'user': self}) def save(self, *args, **kwargs): """ Override save method """ if self.pk: # original password differs from new password if self.password != self.__original_password: self.set_password(self.password) else: self.set_token() super(PlugsAuthModel, self).save(*args, **kwargs) # we can mark fields as abstract to demand subclass to implement them class Meta: abstract = True PK {:J0v plugs_auth/__init__.py__version__ = '0.1.6' PK }TI plugs_auth/admin.py# PK ȎI{$ $ plugs_auth/emails.py""" Email Template Definition """ from urllib.parse import urlencode from plugs_mail.mail import PlugsMail from plugs_auth.settings import plugs_auth_settings as settings class ActivateAccount(PlugsMail): """ Email sent to user after registration with link to activate his account """ template = 'ACTIVATE_ACCOUNT' context = ('User', ) description = 'Email sent to user after registration with link to activate his account' def get_extra_context(self): user = self.context_data.get('user') params = '?' + urlencode({ 'token': user.token }) activate_uri = settings['SITE_ACTIVATE_VIEW'] + params return {'activate_uri': activate_uri} class ResetPassword(PlugsMail): """ Email sent to user with reset password link """ template = 'RESET_PASSWORD' context = ('User', ) description = 'Email sent to user with reset password link' def get_extra_context(self): """ Adds reset_password_uri as extra context """ user = self.context_data.get('user') params = '?' + urlencode({ 'email': user.email, 'token': user.token }) reset_password_uri = settings['SITE_RESET_VIEW'] + params return {'reset_password_uri': reset_password_uri} class AccountActivated(PlugsMail): """ Email sent to user after succesful account activation """ template = 'ACCOUNT_ACTIVATED' context = ('User', ) description = 'Email sent to user after succesful account activation' PK z:J{s plugs_auth/settings.py""" Plugs Auth Settings """ from django.conf import settings from django.core.exceptions import ImproperlyConfigured MANDATORY_SETTINGS = ['RESET_VIEW'] PROJECT_SETTINGS = getattr(settings, 'PLUGS_AUTH', {}) for setting in MANDATORY_SETTINGS: try: PROJECT_SETTINGS[setting] except KeyError: raise ImproperlyConfigured('Missing setting: PLUGS_AUTH[\'{0}\']'.format(setting)) DEFAULTS = { 'USER_ENDPOINT': 'users', 'REQUIRE_ACTIVATION': True } for setting in DEFAULTS.keys(): if setting not in PROJECT_SETTINGS: PROJECT_SETTINGS[setting] = DEFAULTS[setting] # conditional settings, only required if something, something... if PROJECT_SETTINGS['REQUIRE_ACTIVATION'] and 'ACTIVATE_VIEW' not in PROJECT_SETTINGS: raise ImproperlyConfigured('Missing ACTIVATE_VIEW. This setting is required if REQUIRE_ACTIVATION is True') plugs_auth_settings = PROJECT_SETTINGS PK }TIi; plugs_auth/runtests.py#!/usr/bin/env python import os import sys import django from django.conf import settings from django.test.utils import get_runner if __name__ == "__main__": os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_settings' django.setup() TestRunner = get_runner(settings) test_runner = TestRunner() failures = test_runner.run_tests(["tests"]) sys.exit(bool(failures)) PK }TIRKB B ! plugs_auth/tests/test_settings.pySECRET_KEY = 'afakesecret' INSTALLED_APPS = [ "plugs_auth", ] PK }TI@C plugs_auth/tests/test_models.py""" Testing Models """ from django.contrib.auth import get_user_model from plugs_core.testcases import PlugsAPITestCase model = get_user_model() class TestModels(PlugsAPITestCase): """ Testing Models """ def test_superuser_can_login_with_new_account(self): """ Ensures superuser is created and can login """ data = { 'email': 'joe@somewhere.org', 'password': 'longandhardpass' } user = model.objects.create_superuser(**data) # created password should be unusable self.assertTrue(user.has_usable_password()) self.assertLogin(data['email'], data['password']) PK jUI plugs_auth/tests/test_views.py""" Testing Users Endpoint """ from django.contrib.auth import get_user_model from django.core.urlresolvers import reverse from rest_framework.test import APITestCase from plugs_core import utils from plugs_core.testcases import PlugsAPITestCase from plugs_auth.tests.factories import UserFactory model = get_user_model() class TestViews(PlugsAPITestCase): """ Testing Views """ def test_guest_can_activate_account(self): """ Ensures guest can activate account """ # setup # user = UserFactory(is_active=True) user = UserFactory(is_active=False, first_name='Joe') # @FIX url pattern not correct url = reverse('activate-user') + '?token=' + user.token # exercise response = self.client.get(url) # assert self.assert200(response) self.assertEqual(model.objects.get(pk=user.pk).is_active, True) self.assertEqual(response.data, { "message": "Activated"}) def test_guest_can_start_reset_password_process(self): """ Ensures guest can start reset password process """ # setup user = UserFactory(is_active=True) # exercise url = reverse('reset-password') data = {'email': user.email} response = self.client.post(url, data) self.assert200(response) # assert validation token has been refreshed new_token = model.objects.get(pk=user.pk).token self.assertNotEqual(user.token, new_token) def test_guest_can_set_new_password(self): """ Ensures guest can set new password """ # setup user = UserFactory(is_active=True) # exercise data = { 'email': user.email, 'token': user.token, 'password': 'newpassword' } self.client.post(reverse('set-password'), data) self.assertLogin(user.email, 'newpassword') def test_inactive_user_can_reset_password_and_login(self): """ Ensures inactive user can reset password and login """ user = UserFactory(is_active=False) # exercise data = {'email': user.email} response = self.client.post(reverse('reset-password'), data) self.assert200(response) user.refresh_from_db() # exercise data = { 'email': user.email, 'token': user.token, 'password': 'newpassword' } response = self.client.post(reverse('set-password'), data) self.assert200(response) user.refresh_from_db() # assert self.assert200(response) self.assertLogin(user.email, 'newpassword') self.assertEqual(user.is_active, True) def test_guest_cannot_set_new_password_without_token(self): """ Ensures guest cannot set new password without token """ # setup user = UserFactory(is_active=True) # exercise data = { 'email': user.email, 'password': 'newpassword' } response = self.client.post(reverse('set-password'), data) self.assert400(response) self.assertCannotLogin(user.email, 'newpassword') def test_guest_cannot_set_new_password_with_invalid_token(self): """ Ensures guest cannot set new password with invalid token """ # setup user = UserFactory(is_active=True) # exercise data = { 'email': user.email, 'password': 'newpassword', 'token': 'xxx' } response = self.client.post(reverse('set-password'), data) self.assert404(response) self.assertCannotLogin(user.email, 'newpassword') PK }TI plugs_auth/tests/__init__.pyPK }TIrݨ plugs_auth/tests/test_mails.py""" Test Emails """ from django.core.urlresolvers import reverse from plugs_core.testcases import PlugsAPITestCase from plugs_core import utils from plugs_mail.management.commands import load_email_templates from plugs_auth.tests.factories import UserFactory from plugs_auth.settings import plugs_auth_settings class MailTestsBoy(PlugsAPITestCase): def setUp(self): """ Creates email templates """ # uses the load email templates command to # auto populate our test database with email # templates command = load_email_templates.Command() command.handle() def test_email_reset_password_sent_to_user(self): """ Ensures reset password email is sent to user """ # setup user = UserFactory(is_active=False) # exercise data = {'email': user.email} response = self.client.post(reverse('reset-password'), data) self.assert200(response) self.assertEmailCount('Perdeste a tua password?', 1) self.assertEmailTo('Perdeste a tua password?', data.get('email')) def test_email_account_activated_sent_to_user(self): """ Ensures account activated email is sent to user """ # setup user = UserFactory(is_active=False) # @FIX url pattern not correct url = reverse('activate-user') + '?token=' + user.token response = self.client.get(url) # assert self.assert200(response) self.assertEmailCount('Conta ativada com sucesso', 1) self.assertEmailTo('Conta ativada com sucesso', user.email) def test_email_resent_verification_email(self): """ Ensures user receives a new verification email """ user = UserFactory(is_active=False) self.assertEmailCount('Ativa a tua conta', 1) url = reverse('resend-verification') data = {'email': user.email} response = self.client.post(url, data) self.assertEmailCount('Ativa a tua conta', 2) def test_email_resent_verification_email_not_sent_if_user_active(self): """ Ensures user does not receive a new verification email if already active """ user = UserFactory(is_active=True) self.assertEmailCount('Ativa a tua conta', 1) url = reverse('resend-verification') data = {'email': user.email} response = self.client.post(url, data) self.assertEmailCount('Ativa a tua conta', 1) PK }TI7v v plugs_auth/tests/factories.py""" Factory Boy Factory Definition """ from django.contrib.auth import get_user_model from factory.django import DjangoModelFactory as Factory from factory import LazyAttribute, Sequence, SubFactory model = get_user_model() class UserFactory(Factory): """ Base User Factory """ email = LazyAttribute(lambda m: '{0}@example.com'.format(m.first_name)) first_name = Sequence(lambda n: 'User{0}'.format(n)) last_name = 'Smith' token = Sequence(lambda n: 'Validation{0}'.format(n)) # pylint: disable=R0903 class Meta: """ Metaclass Definition """ model = model PK }TI plugs_auth/templates/__init__.pyPK ȎI%tA A 4 plugs_auth/templates/emails/activate_account_pt.htmlAtiva a tua conta {% extends "base.html" %} {% load emailtags %} {% block content %}
Olá {{user.first_name}},
A tua conta foi criada com sucesso. Para começares a usar precisas de confirmar o teu email. Para isso basta clicar no link seguinte:
{% button activate_uri "ativar conta" %} {% endblock %} PK ȎI=w" " 4 plugs_auth/templates/emails/activate_account_en.htmlActivate account {% extends "base.html" %} {% load emailtags %} {% block content %}Hello {{user.first_name}},
Your account was successfully created.
Please confirm your email by clicking the link bellow:
{% button activate_uri "activate account" %} {% endblock %} PK ȎI<