From 098b47b3943db6c76beeba92bd4e3a59bdf00f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 22 Dec 2014 13:07:40 -0300 Subject: [PATCH 1/3] Add compatibility with newer Django versions --- inviter/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inviter/urls.py b/inviter/urls.py index ccf33e3..754bcfb 100644 --- a/inviter/urls.py +++ b/inviter/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls.defaults import patterns, url +from django.conf.urls import patterns, url from inviter.views import Register, Done, OptOut, OptOutDone urlpatterns = patterns('', From 9ee8379865ea97abd34a950e02d9e31b4a94202b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 22 Dec 2014 13:13:16 -0300 Subject: [PATCH 2/3] add compatibility for python3 --- inviter/tests.py | 4 +- inviter/tests.py.bak | 109 ++++++++++++++++++++++++++++++++++++++++++ inviter/tokens.py | 2 +- inviter/tokens.py.bak | 16 +++++++ 4 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 inviter/tests.py.bak create mode 100644 inviter/tokens.py.bak diff --git a/inviter/tests.py b/inviter/tests.py index 8b1e169..7e1c936 100644 --- a/inviter/tests.py +++ b/inviter/tests.py @@ -14,7 +14,7 @@ from inviter.models import OptOut from inviter.utils import invite, token_generator import shortuuid -import urlparse +import urllib.parse @@ -95,7 +95,7 @@ def test_opt_out(self): resp = self.client.post(url, {}) self.assertEqual(302, resp.status_code, resp.status_code) - self.assertEqual(reverse('inviter:opt-out-done'), urlparse.urlparse(resp['Location']).path) + self.assertEqual(reverse('inviter:opt-out-done'), urllib.parse.urlparse(resp['Location']).path) self.assertEqual(2, User.objects.count()) user, sent = invite("foo@example.com", self.inviter) diff --git a/inviter/tests.py.bak b/inviter/tests.py.bak new file mode 100644 index 0000000..8b1e169 --- /dev/null +++ b/inviter/tests.py.bak @@ -0,0 +1,109 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" +from django.conf import settings +from django.contrib.auth import tests +from django.contrib.auth.models import User +from django.core import mail +from django.core.urlresolvers import reverse +from django.test import TestCase +from django.utils.http import int_to_base36 +from inviter.models import OptOut +from inviter.utils import invite, token_generator +import shortuuid +import urlparse + + + +class InviteTest(TestCase): + def setUp(self): + self.original_email_backend = settings.EMAIL_BACKEND + settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' + self.inviter = User.objects.create(username=shortuuid.uuid()) + self.existing = User.objects.create(username=shortuuid.uuid(), + email='existing@example.com') + + def tearDown(self): + settings.EMAIL_BACKEND = self.original_email_backend + + def test_inviting(self): + user, sent = invite("foo@example.com", self.inviter) + self.assertTrue(sent) + self.assertFalse(user.is_active) + self.assertEqual(1, len(mail.outbox)) + self.assertEqual(3, User.objects.count()) + + # Resend the mail + user, sent = invite("foo@example.com", self.inviter) + self.assertTrue(sent) + self.assertFalse(user.is_active) + self.assertEqual(2, len(mail.outbox)) + self.assertEqual(3, User.objects.count()) + + # Don't resend the mail + user, sent = invite("foo@example.com", self.inviter, resend = False) + self.assertFalse(sent) + self.assertFalse(user.is_active) + self.assertEqual(2, len(mail.outbox)) + self.assertEqual(3, User.objects.count()) + + # Don't send the email to active users + user, sent = invite("existing@example.com", self.inviter) + self.assertFalse(sent) + self.assertTrue(user.is_active) + self.assertEqual(2, len(mail.outbox)) + self.assertEqual(3, User.objects.count()) + + def test_views(self): + user, sent = invite("foo@example.com", self.inviter) + self.assertTrue(sent) + url_parts = int_to_base36(user.id), token_generator.make_token(user) + + url = reverse('inviter:register', args=url_parts) + + resp = self.client.get(url) + + self.assertEqual(200, resp.status_code, resp.status_code) + + resp = self.client.post(url, {'username': 'testuser', 'email': 'foo@example.com', + 'new_password1': 'test-1234', 'new_password2': 'test-1234'}) + + self.assertEqual(302, resp.status_code, resp.content) + + self.client.login(username='testuser', password='test-1234') + + resp = self.client.get(reverse('inviter:done')) + + self.assertEqual(200, resp.status_code, resp.status_code) + + def test_opt_out(self): + self.assertEqual(2, User.objects.count()) + + user, sent = invite("foo@example.com", self.inviter) + self.assertTrue(sent) + self.assertEqual(1, len(mail.outbox)) + self.assertEqual(3, User.objects.count()) + + url_parts = int_to_base36(user.id), token_generator.make_token(user) + url = reverse('inviter:opt-out', args=url_parts) + + resp = self.client.get(url) + self.assertEqual(200, resp.status_code, resp.status_code) + + resp = self.client.post(url, {}) + self.assertEqual(302, resp.status_code, resp.status_code) + self.assertEqual(reverse('inviter:opt-out-done'), urlparse.urlparse(resp['Location']).path) + self.assertEqual(2, User.objects.count()) + + user, sent = invite("foo@example.com", self.inviter) + self.assertFalse(sent) + self.assertEqual(1, len(mail.outbox)) + self.assertEqual(1, OptOut.objects.count()) + self.assertTrue(OptOut.objects.is_blocked("foo@example.com")) + self.assertIsNone(user) + + + diff --git a/inviter/tokens.py b/inviter/tokens.py index d06f5a3..d345ee6 100644 --- a/inviter/tokens.py +++ b/inviter/tokens.py @@ -6,7 +6,7 @@ def make_token(self, user): return self._make_token(user) def _make_token(self, user): - value = u'-'.join([unicode(user.id), unicode(user.last_login), + value = '-'.join([str(user.id), str(user.last_login), user.password]) return salted_hmac(settings.SECRET_KEY, value).hexdigest()[::2] diff --git a/inviter/tokens.py.bak b/inviter/tokens.py.bak new file mode 100644 index 0000000..d06f5a3 --- /dev/null +++ b/inviter/tokens.py.bak @@ -0,0 +1,16 @@ +from django.conf import settings +from django.utils.crypto import salted_hmac, constant_time_compare + +class TokenGenerator(object): + def make_token(self, user): + return self._make_token(user) + + def _make_token(self, user): + value = u'-'.join([unicode(user.id), unicode(user.last_login), + user.password]) + return salted_hmac(settings.SECRET_KEY, value).hexdigest()[::2] + + def check_token(self, user, token): + return constant_time_compare(token, self._make_token(user)) + +generator = TokenGenerator() \ No newline at end of file From 00191aa3687d715700c8d0e507164735d53f9996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Tue, 30 Dec 2014 16:21:51 -0300 Subject: [PATCH 3/3] =?UTF-8?q?se=20romp=C3=ADa=20cuando=20exist=C3=ADan?= =?UTF-8?q?=20dos=20usuarios=20con=20un=20mismo=20email?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inviter/tokens.py | 10 +++++--- inviter/utils.py | 63 ++++++++++++++++++++++++----------------------- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/inviter/tokens.py b/inviter/tokens.py index d345ee6..f00e549 100644 --- a/inviter/tokens.py +++ b/inviter/tokens.py @@ -1,16 +1,18 @@ from django.conf import settings from django.utils.crypto import salted_hmac, constant_time_compare + class TokenGenerator(object): def make_token(self, user): return self._make_token(user) - + def _make_token(self, user): value = '-'.join([str(user.id), str(user.last_login), user.password]) return salted_hmac(settings.SECRET_KEY, value).hexdigest()[::2] - + def check_token(self, user, token): return constant_time_compare(token, self._make_token(user)) - -generator = TokenGenerator() \ No newline at end of file + +generator = TokenGenerator() + diff --git a/inviter/utils.py b/inviter/utils.py index 1e332f3..18e228f 100644 --- a/inviter/utils.py +++ b/inviter/utils.py @@ -16,11 +16,11 @@ def send_invite(invitee, inviter, url=None, opt_out_url=None, **kwargs): """ - Send the default invitation email assembled from + Send the default invitation email assembled from ``inviter/email/subject.txt`` and ``inviter/email/body.txt`` - + Both templates will receive all the ``kwargs``. - + :param invitee: The invited user :param inviter: The inviting user :param url: The invite URL @@ -32,68 +32,69 @@ def send_invite(invitee, inviter, url=None, opt_out_url=None, **kwargs): ctx.update(kwargs) ctx.update(site=Site.objects.get_current(), url=url) ctx = template.Context(ctx) - + subject_template = kwargs.pop('subject_template', 'inviter/email/subject.txt') body_template = kwargs.pop('body_template', 'inviter/email/body.txt') - + subject = template.loader.get_template(subject_template) body = template.loader.get_template(body_template) - + subject = subject.render(ctx) body = body.render(ctx) - + subject = ' '.join(subject.split('\n')) # No newlines in subject lines allowed - + send_mail(subject, body, FROM_EMAIL, [invitee.email]) def invite(email, inviter, sendfn=send_invite, resend=True, **kwargs): """ Invite a given email address and return a ``(User, sent)`` tuple similar to the Django :meth:`django.db.models.Manager.get_or_create` method. - + If a user with ``email`` address does not exist: - - * Creates a :class:`django.contrib.auth.models.User` object + + * Creates a :class:`django.contrib.auth.models.User` object * Set ``user.email = email`` * Set ``user.is_active = False`` * Set a random password * Send the invitation email * Return ``(user, True)`` - + If a user with ``email`` address exists and ``user.is_active == False``: - - * Re-send the invitation + + * Re-send the invitation * Return ``(user, True)`` - + If a user with ``email`` address exists: - + * Don't send the invitation * Return ``(user, False)`` - + If the email address is blocked: - + * Don't send the invitation * Return ``(None, False)`` - + To customize sending, pass in a new ``sendfn`` function as documented by :attr:`inviter.utils.send_invite`: - + :: - + sendfn = lambda invitee, inviter, **kwargs: 1 - invite("foo@bar.com", request.user, sendfn = sendfn) + invite("foo@bar.com", request.user, sendfn = sendfn) + - :param email: The email address :param inviter: The user inviting the email address :param sendfn: An email sending function. Defaults to :attr:`inviter.utils.send_invite` - :param resend: Resend email to users that are not registered yet + :param resend: Resend email to users that are not registered yet """ - - if OptOut.objects.is_blocked(email): + + if OptOut.objects.is_blocked(email): return None, False try: - user = User.objects.get(email=email) + # Por si existen muchos usuarios con el mismo email + user = User.objects.filter(email=email).first() if user.is_active: return user, False if not resend: @@ -106,13 +107,13 @@ def invite(email, inviter, sendfn=send_invite, resend=True, **kwargs): ) user.set_unusable_password() user.save() - + url_parts = int_to_base36(user.id), token_generator.make_token(user) url = reverse('inviter:register', args=url_parts) - + opt_out_url = reverse('inviter:opt-out', args=url_parts) kwargs.update(opt_out_url=opt_out_url) - + sendfn(user, inviter, url=url, **kwargs) return user, True - +