Commits

Peter Sagerson committed c2a8bcc

Python 3 compatibility.

Versions are bumped to 0.2 and the minimum Django requirement is now 1.4.

Comments (0)

Files changed (38)

+[report]
+omit = */tests.py
+       */tests/*
+       */admin.py
+       */management/*

django-otp-agents/otp_agents/tests/__init__.py

         try:
             get_app('otp_static')
         except:
-            self.skipTest(u"Requires django_otp.plugins.otp_static")
+            self.skipTest("Requires django_otp.plugins.otp_static")
 
         try:
             self.alice = self.create_user('alice', 'alice')
         except IntegrityError:
-            self.skipTest(u"Unable to create a test user")
+            self.skipTest("Unable to create a test user")
         else:
             device = self.alice.staticdevice_set.create()
             device.token_set.create(token='alice1')
     def test_otp_anonymous(self):
         response = self.client.get('/otp/')
 
-        self.assertEquals(response.status_code, 302)
+        self.assertEqual(response.status_code, 302)
 
     def test_otp_authenticated(self):
         self.login()
         response = self.client.get('/otp/')
 
-        self.assertEquals(response.status_code, 302)
+        self.assertEqual(response.status_code, 302)
 
     def test_otp_verified(self):
         self.verify()
         response = self.client.get('/otp/')
 
-        self.assertEquals(response.status_code, 200)
+        self.assertEqual(response.status_code, 200)
 
     def test_otp_trusted(self):
         self.trust(True)
         self.login()
         response = self.client.get('/otp/')
 
-        self.assertEquals(response.status_code, 302)
+        self.assertEqual(response.status_code, 302)
 
     def test_otp2_verified(self):
         self.verify()
         response = self.client.get('/otp2/')
 
-        self.assertEquals(response.status_code, 200)
+        self.assertEqual(response.status_code, 200)
 
     def test_otp_advised_anonymous(self):
         response = self.client.get('/otp_advised/')
 
-        self.assertEquals(response.status_code, 302)
+        self.assertEqual(response.status_code, 302)
 
     def test_otp_advised_unconfigured(self):
         self.alice.staticdevice_set.all().delete()
         self.login()
         response = self.client.get('/otp_advised/')
 
-        self.assertEquals(response.status_code, 200)
+        self.assertEqual(response.status_code, 200)
 
     def test_otp_advised_unconfigured_2(self):
         self.alice.staticdevice_set.all().delete()
         self.login()
         response = self.client.get('/otp_advised_2/')
 
-        self.assertEquals(response.status_code, 200)
+        self.assertEqual(response.status_code, 200)
 
     def test_otp_advised_authenticated(self):
         self.login()
         response = self.client.get('/otp_advised/')
 
-        self.assertEquals(response.status_code, 302)
+        self.assertEqual(response.status_code, 302)
 
     def test_otp_advised_verified(self):
         self.verify()
         response = self.client.get('/otp_advised/')
 
-        self.assertEquals(response.status_code, 200)
+        self.assertEqual(response.status_code, 200)
 
     def test_agent_anonymous(self):
         response = self.client.get('/agent/')
 
-        self.assertEquals(response.status_code, 302)
+        self.assertEqual(response.status_code, 302)
 
     def test_agent_authenticated(self):
         response = self.client.get('/agent/')
 
-        self.assertEquals(response.status_code, 302)
+        self.assertEqual(response.status_code, 302)
 
     def test_agent_verified(self):
         self.verify()
         response = self.client.get('/agent/')
 
-        self.assertEquals(response.status_code, 200)
+        self.assertEqual(response.status_code, 200)
 
     def test_agent_trusted_session(self):
         self.trust(False)
         response = self.client.get('/agent/')
 
-        self.assertEquals(response.status_code, 200)
+        self.assertEqual(response.status_code, 200)
 
     def test_agent_trusted(self):
         self.trust(True)
         self.login()
         response = self.client.get('/agent/')
 
-        self.assertEquals(response.status_code, 200)
+        self.assertEqual(response.status_code, 200)
 
     def test_two_step_trust(self):
         self.login()
         self.login()
         response = self.client.get('/agent/')
 
-        self.assertEquals(response.status_code, 200)
+        self.assertEqual(response.status_code, 200)
 
     def login(self):
         params = {
 
         response = self.client.post('/login/', params)
 
-        self.assertEquals(response.status_code, 302)
+        self.assertEqual(response.status_code, 302)
 
     def verify(self):
         params = {
 
         response = self.client.post('/verify/', params)
 
-        self.assertEquals(response.status_code, 302)
+        self.assertEqual(response.status_code, 302)
 
     def trust(self, persist=False):
         params = {
 
         response = self.client.post('/trust/', params)
 
-        self.assertEquals(response.status_code, 302)
+        self.assertEqual(response.status_code, 302)
 
     def add_trust(self, persist=False):
         params = {
 
         response = self.client.post('/trust/', params)
 
-        self.assertEquals(response.status_code, 302)
+        self.assertEqual(response.status_code, 302)
 
     def logout(self):
         response = self.client.post('/logout/')
 
-        self.assertEquals(response.status_code, 200)
+        self.assertEqual(response.status_code, 200)

django-otp-agents/otp_agents/tests/urls.py

 from . import views
 
 
-urlpatterns = patterns('',
+urlpatterns = patterns(
+    '',
+
     url(r'^login/$', 'django.contrib.auth.views.login'),
     url(r'^verify/$', 'django_otp.views.login'),
     url(r'^trust/$', 'otp_agents.views.login'),

django-otp-agents/setup.py

 
 setup(
     name='django-otp-agents',
-    version='0.1.4',
+    version='0.2.0',
     description="Integration of django-otp and django-agent-trust.",
     long_description=open('README').read(),
     author='Peter Sagerson',
     url='https://bitbucket.org/psagers/django-otp',
     license='BSD',
     install_requires=[
-        'django-otp >= 0.1.8',
-        'django-agent-trust',
+        'django-otp >= 0.2.0',
+        'django-agent-trust >= 0.1.8',
     ],
     classifiers=[
         "Development Status :: 4 - Beta",
         "Programming Language :: Python :: 2",
+        "Programming Language :: Python :: 2.6",
+        "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.2",
+        "Programming Language :: Python :: 3.3",
         "Intended Audience :: Developers",
         "License :: OSI Approved :: BSD License",
         "Topic :: Security",
         "Topic :: Software Development :: Libraries :: Python Modules",
+        "Framework :: Django",
     ],
 )

django-otp-twilio/otp_twilio/conf.py

 import django.conf
 
+from django.utils.six import iteritems
+
 
 class Settings(object):
     """
         Loads our settings from django.conf.settings, applying defaults for any
         that are omitted.
         """
-        for name, default in self.defaults.iteritems():
+        for name, default in iteritems(self.defaults):
             value = getattr(django.conf.settings, name, default)
             setattr(self, name, value)
 

django-otp-twilio/otp_twilio/models.py

 
         *CharField*: The secret key used to generate TOTP tokens.
     """
-    number = models.CharField(max_length=16,
+    number = models.CharField(
+        max_length=16,
         help_text="The mobile number to deliver tokens to."
     )
 
-    key = models.CharField(max_length=40,
+    key = models.CharField(
+        max_length=40,
         validators=[hex_validator(20)],
         default=lambda: random_hex(20),
         help_text="A random key used to generate tokens (hex-encoded)."
 
     @property
     def bin_key(self):
-        return unhexlify(self.key)
+        return unhexlify(self.key.encode())
 
     def generate_challenge(self):
         """
         else:
             self._deliver_token(token)
 
-        return u'Sent by SMS'
+        return "Sent by SMS"
 
     def _deliver_token(self, token):
         self._validate_config()
             'Body': str(token),
         }
 
-        response = requests.post(url, data=data,
+        response = requests.post(
+            url, data=data,
             auth=(settings.OTP_TWILIO_ACCOUNT, settings.OTP_TWILIO_AUTH)
         )
 
     def verify_token(self, token):
         try:
             token = int(token)
-        except StandardError:
+        except Exception:
             return False
         else:
             return any(totp(self.bin_key, drift=drift) == token for drift in [0, -1])

django-otp-twilio/otp_twilio/tests.py

-from django.contrib.auth.models import User
 from django.db import IntegrityError
 
 from django_otp.oath import totp
             self.alice = self.create_user('alice', 'password')
             self.bob = self.create_user('bob', 'password')
         except IntegrityError:
-            self.skipTest(u"Unable to create test users.")
+            self.skipTest("Unable to create test users.")
         else:
             self.alice.twiliosmsdevice_set.create(number='test',
-                key='01234567890123456789')
+                                                  key='01234567890123456789')
             self.bob.twiliosmsdevice_set.create(number='test',
-                key='98765432109876543210')
+                                                key='98765432109876543210')
 
     def test_current(self):
         device = self.alice.twiliosmsdevice_set.get()
         token = device.generate_challenge()
         ok = device.verify_token(token)
 
-        self.assert_(ok)
+        self.assertTrue(ok)
 
     def test_previous(self):
         device = self.alice.twiliosmsdevice_set.get()
         token = totp(device.bin_key, t0=30)
         ok = device.verify_token(token)
 
-        self.assert_(ok)
+        self.assertTrue(ok)
 
     def test_past(self):
         device = self.alice.twiliosmsdevice_set.get()
         token = totp(device.bin_key, t0=60)
         ok = device.verify_token(token)
 
-        self.assert_(not ok)
+        self.assertTrue(not ok)
 
     def test_future(self):
         device = self.alice.twiliosmsdevice_set.get()
         token = totp(device.bin_key, t0=-30)
         ok = device.verify_token(token)
 
-        self.assert_(not ok)
+        self.assertTrue(not ok)
 
     def test_cross_user(self):
         device = self.alice.twiliosmsdevice_set.get()
         token = device.generate_challenge()
         ok = self.bob.twiliosmsdevice_set.get().verify_token(token)
 
-        self.assert_(not ok)
+        self.assertTrue(not ok)

django-otp-twilio/setup.py

 
 setup(
     name='django-otp-twilio',
-    version='0.1.3',
+    version='0.2.0',
     description="A django-otp plugin that delivers tokens via Twilio's SMS service.",
     long_description=open('README').read(),
     author='Peter Sagerson',
     url='https://bitbucket.org/psagers/django-otp',
     license='BSD',
     install_requires=[
-        'django-otp',
+        'django-otp >= 0.2.0',
         'requests',
     ],
     classifiers=[
         "Development Status :: 4 - Beta",
         "Programming Language :: Python :: 2",
+        "Programming Language :: Python :: 2.6",
+        "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.2",
+        "Programming Language :: Python :: 3.3",
         "Intended Audience :: Developers",
         "License :: OSI Approved :: BSD License",
         "Topic :: Security",
         "Topic :: Software Development :: Libraries :: Python Modules",
+        "Framework :: Django",
     ],
 )

django-otp-yubikey/otp_yubikey/models.py

         *PositiveIntegerField*: The volatile session usage counter most
         recently used by this device.
     """
-    private_id = models.CharField(max_length=12,
+    private_id = models.CharField(
+        max_length=12,
         validators=[hex_validator(6)],
         default=lambda: random_hex(6),
         verbose_name="Private ID",
         help_text="The 6-byte private ID (hex-encoded)."
     )
 
-    key = models.CharField(max_length=32,
+    key = models.CharField(
+        max_length=32,
         validators=[hex_validator(16)],
         default=lambda: random_hex(16),
         help_text="The 16-byte AES key shared with this YubiKey (hex-encoded)."
 
     @property
     def bin_key(self):
-        return unhexlify(self.key)
+        return unhexlify(self.key.encode())
 
     def verify_token(self, token):
         try:
             public_id, otp = decode_otp(token, self.bin_key)
-        except StandardError:
+        except Exception:
             return False
 
         if public_id != self.public_id():
             return False
 
-        if hexlify(otp.uid) != self.private_id:
+        if hexlify(otp.uid) != self.private_id.encode():
             return False
 
         if otp.session < self.session:
     """
     API_VERSIONS = ['1.0', '1.1', '2.0']
 
-    name = models.CharField(max_length=32,
+    name = models.CharField(
+        max_length=32,
         help_text="The name of this validation service."
     )
 
         help_text="Your API ID."
     )
 
-    api_key = models.CharField(max_length=64,
+    api_key = models.CharField(
+        max_length=64,
         blank=True,
         default='',
         verbose_name="API key",
         help_text="The base URL of the verification service. Defaults to Yubico's hosted API."
     )
 
-    api_version = models.CharField(max_length=8,
-        choices=zip(API_VERSIONS, API_VERSIONS),
+    api_version = models.CharField(
+        max_length=8,
+        choices=list(zip(API_VERSIONS, API_VERSIONS)),
         default='2.0',
         help_text="The version of the validation api to use."
     )
         help_text="Use HTTPS API URLs by default?"
     )
 
-    param_sl = models.CharField(max_length=16,
+    param_sl = models.CharField(
+        max_length=16,
         blank=True,
         default=None,
         verbose_name="SL",
         help_text="The level of syncing required."
     )
 
-    param_timeout = models.CharField(max_length=16,
+    param_timeout = models.CharField(
+        max_length=16,
         blank=True,
         default=None,
         verbose_name="Timeout",
         return self.name
 
     def get_client(self):
-        api_key = b64decode(self.api_key) or None
+        api_key = b64decode(self.api_key.encode()) or None
 
         if self.api_version == '2.0':
             client = YubiClient20(self.api_id, api_key, self.use_ssl, False, self.param_sl or None, self.param_timeout or None)

django-otp-yubikey/otp_yubikey/tests.py

 
 
 class YubikeyTest(TestCase):
-    alice_public = 'cccccccb'
-    alice_aes = unhexlify('fb362a0853be5e5306d5cc2483f279cb')
-    alice_key = YubiKey(unhexlify('5dc30490956b'), 6, 0)
+    alice_public = b'cccccccb'
+    alice_aes = unhexlify(b'fb362a0853be5e5306d5cc2483f279cb')
+    alice_key = YubiKey(unhexlify(b'5dc30490956b'), 6, 0)
 
-    bob_public = 'cccccccd'
-    bob_key = YubiKey(unhexlify('326f70826d31'), 11, 0)
-    bob_aes = unhexlify('11080a0e7a56d0a1546f327f20626308')
+    bob_public = b'cccccccd'
+    bob_key = YubiKey(unhexlify(b'326f70826d31'), 11, 0)
+    bob_aes = unhexlify(b'11080a0e7a56d0a1546f327f20626308')
 
     def setUp(self):
         try:
             alice = self.create_user('alice', 'password')
             bob = self.create_user('bob', 'password')
         except IntegrityError:
-            self.skipTest(u"Unable to create the test user")
+            self.skipTest("Unable to create the test user")
         else:
             self.alice_device = alice.yubikeydevice_set.create(
                 private_id='5dc30490956b',
         _, token = self.alice_token()
         ok = self.alice_device.verify_token(token)
 
-        self.assert_(ok)
+        self.assertTrue(ok)
 
     def test_counter_increment(self):
         otp, token = self.alice_token(5, 7)
         ok = self.alice_device.verify_token(token)
 
-        self.assert_(ok)
+        self.assertTrue(ok)
         self.assertEqual(self.alice_device.session, 5)
         self.assertEqual(self.alice_device.counter, 7)
 
         _, token = self.alice_token()
         ok = self.bob_device.verify_token(token)
 
-        self.assert_(not ok)
+        self.assertTrue(not ok)
 
     def test_replay(self):
         otp, token = self.alice_token()
         ok1 = self.alice_device.verify_token(token)
         ok2 = self.alice_device.verify_token(token)
 
-        self.assert_(ok1)
-        self.assert_(not ok2)
+        self.assertTrue(ok1)
+        self.assertTrue(not ok2)
         self.assertEqual(self.alice_device.session, otp.session)
         self.assertEqual(self.alice_device.counter, otp.counter)
 
         otp, token = self.alice_token()
         ok = self.alice_device.verify_token(token)
 
-        self.assert_(not ok)
+        self.assertTrue(not ok)
 
     def test_bad_private_id(self):
-        alice_key = YubiKey(unhexlify('2627dc624cbd'), 6, 0)
+        alice_key = YubiKey(unhexlify(b'2627dc624cbd'), 6, 0)
         otp = alice_key.generate()
         token = encode_otp(otp, self.alice_aes, self.alice_public)
         ok = self.alice_device.verify_token(token)
 
-        self.assert_(not ok)
+        self.assertTrue(not ok)
 
     def test_session_replay(self):
         otp, token = self.alice_token(4, 0)
         ok = self.alice_device.verify_token(token)
 
-        self.assert_(not ok)
+        self.assertTrue(not ok)
 
     def test_counter_replay(self):
         otp, token = self.alice_token(5, 0)
         ok = self.alice_device.verify_token(token)
 
-        self.assert_(not ok)
+        self.assertTrue(not ok)
 
     def test_bad_decrypt(self):
         otp = self.alice_key.generate()
         token = encode_otp(otp, self.bob_aes, self.alice_public)
         ok = self.alice_device.verify_token(token)
 
-        self.assert_(not ok)
+        self.assertTrue(not ok)
 
     def test_bogus_token(self):
         ok = self.alice_device.verify_token('completelybogus')
 
-        self.assert_(not ok)
+        self.assertTrue(not ok)
 
     def alice_token(self, session=None, counter=None):
         otp = self.alice_key.generate()

django-otp-yubikey/setup.py

 
 setup(
     name='django-otp-yubikey',
-    version='0.1.2',
+    version='0.2.0',
     description='A django-otp plugin that verifies YubiKey OTP tokens.',
     long_description=open('README').read(),
     author='Peter Sagerson',
     url='https://bitbucket.org/psagers/django-otp',
     license='BSD',
     install_requires=[
-        'django-otp',
-        'YubiOTP>=0.2',
+        'django-otp >= 0.2.0',
+        'YubiOTP >= 0.2.1',
     ],
     classifiers=[
         "Development Status :: 4 - Beta",
         "Programming Language :: Python :: 2",
+        "Programming Language :: Python :: 2.6",
+        "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.2",
+        "Programming Language :: Python :: 3.3",
         "Intended Audience :: Developers",
         "License :: OSI Approved :: BSD License",
         "Topic :: Security",
         "Topic :: Software Development :: Libraries :: Python Modules",
+        "Framework :: Django",
     ],
 )

django-otp/django_otp/__init__.py

 import sys
-from itertools import ifilter
 
 from django.contrib.auth.signals import user_logged_in
 from django.db.models import get_apps, get_models
     :returns: The device that accepted ``token``, if any.
     :rtype: :class:`~django_otp.models.Device` or ``None``
     """
-    matches = ifilter(lambda d: d.verify_token(token), devices_for_user(user))
+    matches = (d for d in devices_for_user(user) if d.verify_token(token))
 
     return next(matches, None)
 

django-otp/django_otp/conf.py

 import django.conf
+from django.utils.six import iteritems
 
 
 class Settings(object):
         Loads our settings from django.conf.settings, applying defaults for any
         that are omitted.
         """
-        for name, default in self.defaults.iteritems():
+        for name, default in iteritems(self.defaults):
             value = getattr(django.conf.settings, name, default)
             setattr(self, name, value)
 

django-otp/django_otp/decorators.py

 from django.contrib.auth.decorators import user_passes_test
 
-from django_otp import devices_for_user, user_has_device
+from django_otp import user_has_device
 from django_otp.conf import settings
 
 

django-otp/django_otp/models.py

 from django.conf import settings
 from django.db import models
+from django.utils import six
 
 
 class DeviceManager(models.Manager):
 
         A :class:`~django_otp.models.DeviceManager`.
     """
-    user = models.ForeignKey(getattr(settings, 'AUTH_USER_MODEL', 'auth.User'), help_text=u"The user that this device belongs to.")
-    name = models.CharField(max_length=64, help_text=u"The human-readable name of this device.")
-    confirmed = models.BooleanField(default=True, help_text=u"Is this device ready for use?")
+    user = models.ForeignKey(getattr(settings, 'AUTH_USER_MODEL', 'auth.User'), help_text="The user that this device belongs to.")
+    name = models.CharField(max_length=64, help_text="The human-readable name of this device.")
+    confirmed = models.BooleanField(default=True, help_text="Is this device ready for use?")
 
     objects = DeviceManager()
 
     class Meta(object):
         abstract = True
 
+    def __str__(self):
+        if six.PY3:
+            return self.__unicode__()
+        else:
+            return self.__unicode__().encode('utf-8')
+
     def __unicode__(self):
-        return u'{0}: {1}'.format(self.user.username, self.name)
+        return six.u('{0}: {1}'.format(self.user.username, self.name))
 
     @property
     def persistent_id(self):

django-otp/django_otp/oath.py

 from struct import pack
 from time import time
 
+from django.utils import six
+
+if six.PY3:
+    iterbytes = iter
+else:
+    iterbytes = lambda buf: (ord(b) for b in buf)
+
 
 def hotp(key, counter, digits=6):
     """
     Implementation of the HOTP algorithm from `RFC 4226
     <http://tools.ietf.org/html/rfc4226#section-5>`_.
 
-    :param string key: The shared secret. A 20-byte string is recommended.
+    :param bytes key: The shared secret. A 20-byte string is recommended.
     :param int counter: The password counter.
     :param int digits: The number of decimal digits to generate.
 
     :returns: The HOTP token.
     :rtype: int
 
-    >>> key = '12345678901234567890'
+    >>> key = b'12345678901234567890'
     >>> for c in range(10):
     ...     hotp(key, c)
     755224
     """
     msg = pack('>Q', counter)
     hs = hmac.new(key, msg, sha1).digest()
-    hs = map(ord, hs)
+    hs = list(iterbytes(hs))
 
     offset = hs[19] & 0x0f
-    bin_code = (hs[offset] & 0x7f) << 24 | hs[offset+1] << 16 | hs[offset+2] << 8 | hs[offset+3]
+    bin_code = (hs[offset] & 0x7f) << 24 | hs[offset + 1] << 16 | hs[offset + 2] << 8 | hs[offset + 3]
     hotp = bin_code % pow(10, digits)
 
     return hotp
     Implementation of the TOTP algorithm from `RFC 6238
     <http://tools.ietf.org/html/rfc6238#section-4>`_.
 
-    :param string key: The shared secret. A 20-byte string is recommended.
+    :param bytes key: The shared secret. A 20-byte string is recommended.
     :param int step: The time step in seconds. The time-based code changes
         every ``step`` seconds.
     :param int t0: The Unix time at which to start counting time steps.
     :returns: The TOTP token.
     :rtype: int
 
-    >>> key = '12345678901234567890'
-    >>> now = long(time())
+    >>> key = b'12345678901234567890'
+    >>> now = int(time())
     >>> for delta in range(0, 200, 20):
     ...     totp(key, t0=(now-delta))
     755224
     254676
     287922
     """
-    t = ((long(time()) - t0) / step) + drift
+    t = ((int(time()) - t0) // step) + drift
 
     return hotp(key, t, digits=digits)

django-otp/django_otp/plugins/otp_email/conf.py

 import django.conf
+from django.utils.six import iteritems
 
 
 class OTPEmailSettings(object):
         Loads our settings from django.conf.settings, applying defaults for any
         that are omitted.
         """
-        for name, default in self.defaults.iteritems():
+        for name, default in iteritems(self.defaults):
             value = getattr(django.conf.settings, name, default)
             setattr(self, name, value)
 

django-otp/django_otp/plugins/otp_email/models.py

     key = models.CharField(max_length=80,
                            validators=[hex_validator()],
                            default=lambda: random_hex(20),
-                           help_text=u'A hex-encoded secret key of up to 20 bytes.')
+                           help_text='A hex-encoded secret key of up to 20 bytes.')
 
     @property
     def bin_key(self):
-        return unhexlify(self.key)
+        return unhexlify(self.key.encode())
 
     def generate_challenge(self):
         token = totp(self.bin_key)
                   settings.OTP_EMAIL_SENDER,
                   [self.user.email])
 
-        message = u'sent by email'
+        message = "sent by email"
 
         return message
 
     def verify_token(self, token):
         try:
             token = int(token)
-        except StandardError:
+        except Exception:
             verified = False
         else:
             verified = any(totp(self.bin_key, drift=drift) == token for drift in [0, -1])

django-otp/django_otp/plugins/otp_email/tests.py

         try:
             alice = self.create_user('alice', 'password')
         except IntegrityError:
-            self.skipTest(u"Failed to create user.")
+            self.skipTest("Failed to create user.")
         else:
             alice.emaildevice_set.create()
 
         if not hasattr(alice, 'email'):
-            self.skipTest(u"User model has no email.")
+            self.skipTest("User model has no email.")
 
     def test_email_interaction(self):
         data = {
         }
         form = OTPAuthenticationForm(None, data)
 
-        self.assert_(not form.is_valid())
+        self.assertTrue(not form.is_valid())
         alice = form.get_user()
-        self.assert_(alice.get_username() == 'alice')
-        self.assert_(alice.otp_device is None)
+        self.assertTrue(alice.get_username() == 'alice')
+        self.assertTrue(alice.otp_device is None)
         self.assertEqual(len(mail.outbox), 1)
 
         data['otp_token'] = mail.outbox[0].body
         del data['otp_challenge']
         form = OTPAuthenticationForm(None, data)
 
-        self.assert_(form.is_valid())
-        self.assert_(isinstance(form.get_user().otp_device, EmailDevice))
+        self.assertTrue(form.is_valid())
+        self.assertTrue(isinstance(form.get_user().otp_device, EmailDevice))

django-otp/django_otp/plugins/otp_hotp/models.py

 
         *BigIntegerField*: The next counter value to expect. (Initial: 0)
     """
-    key = models.CharField(max_length=80, validators=[hex_validator()], default=lambda: random_hex(20), help_text=u"A hex-encoded secret key of up to 40 bytes.")
-    digits = models.PositiveSmallIntegerField(choices=[(6,6), (8,8)], default=6, help_text=u"The number of digits to expect in a token.")
-    tolerance = models.PositiveSmallIntegerField(default=5, help_text=u"The number of missed tokens to tolerate.")
-    counter = models.BigIntegerField(default=0, help_text=u"The next counter value to expect.")
+    key = models.CharField(max_length=80, validators=[hex_validator()], default=lambda: random_hex(20), help_text="A hex-encoded secret key of up to 40 bytes.")
+    digits = models.PositiveSmallIntegerField(choices=[(6, 6), (8, 8)], default=6, help_text="The number of digits to expect in a token.")
+    tolerance = models.PositiveSmallIntegerField(default=5, help_text="The number of missed tokens to tolerate.")
+    counter = models.BigIntegerField(default=0, help_text="The next counter value to expect.")
 
     class Meta(Device.Meta):
-        verbose_name = u"HOTP device"
+        verbose_name = "HOTP device"
 
     @property
     def bin_key(self):
         """
         The secret key as a binary string.
         """
-        return unhexlify(self.key)
+        return unhexlify(self.key.encode())
 
     def verify_token(self, token):
         try:
             token = int(token)
-        except StandardError:
+        except Exception:
             verified = False
         else:
             key = self.bin_key

django-otp/django_otp/plugins/otp_hotp/tests.py

         try:
             alice = self.create_user('alice', 'password')
         except IntegrityError:
-            self.skipTest(u"Unable to create test user.")
+            self.skipTest("Unable to create test user.")
         else:
             self.device = alice.hotpdevice_set.create(
                 key='d2e8a68036f68960b1c30532bb6c56da5934d879', digits=6,
     def test_normal(self):
         ok = self.device.verify_token(self.tokens[0])
 
-        self.assert_(ok)
+        self.assertTrue(ok)
         self.assertEqual(self.device.counter, 1)
 
     def test_normal_drift(self):
         ok = self.device.verify_token(self.tokens[1])
 
-        self.assert_(ok)
+        self.assertTrue(ok)
         self.assertEqual(self.device.counter, 2)
 
     def test_excessive_drift(self):
         ok = self.device.verify_token(self.tokens[2])
 
-        self.assert_(not ok)
+        self.assertTrue(not ok)
         self.assertEqual(self.device.counter, 0)
 
     def test_bad_value(self):
         ok = self.device.verify_token(123456)
 
-        self.assert_(not ok)
+        self.assertTrue(not ok)
         self.assertEqual(self.device.counter, 0)

django-otp/django_otp/plugins/otp_static/management/commands/addstatictoken.py

 
 class Command(BaseCommand):
     option_list = BaseCommand.option_list + (
-        make_option('-t', '--token', dest='token', help=u'The token to add. If omitted, one will be randomly generated.'),
+        make_option('-t', '--token', dest='token', help='The token to add. If omitted, one will be randomly generated.'),
     )
-    args = u'<username>'
-    help = fill(u'Adds a single static OTP token to the given user. '
-        u'The token will be added to an arbitrary static device '
-        u'attached to the user, creating one if necessary.', width=78)
+    args = '<username>'
+    help = fill('Adds a single static OTP token to the given user. '
+                'The token will be added to an arbitrary static device '
+                'attached to the user, creating one if necessary.', width=78)
 
     def handle(self, *args, **options):
         if len(args) != 1:
 
         device = next(StaticDevice.objects.filter(user=user).iterator(), None)
         if device is None:
-            device = StaticDevice.objects.create(user=user, name=u'Backup Code')
+            device = StaticDevice.objects.create(user=user, name='Backup Code')
 
         token = options.get('token')
         if token is None:

django-otp/django_otp/plugins/otp_static/models.py

     """
     def verify_token(self, token):
         try:
-            match = self.token_set.filter(token=token).iterator().next()
+            match = next(self.token_set.filter(token=token).iterator())
             match.delete()
         except StopIteration:
             match = None

django-otp/django_otp/plugins/otp_static/tests.py

             try:
                 user = self.create_user(username, 'password')
             except IntegrityError:
-                self.skipTest(u"Unable to create a test user.")
+                self.skipTest("Unable to create a test user.")
             else:
                 device = user.staticdevice_set.create(id=device_id + 1)
                 device.token_set.create(token=username + '1')
         data = {}
         form = OTPAuthenticationForm(None, data)
 
-        self.assert_(not form.is_valid())
+        self.assertTrue(not form.is_valid())
         self.assertEqual(form.get_user(), None)
 
     def test_bad_password(self):
         }
         form = OTPAuthenticationForm(None, data)
 
-        self.assert_(not form.is_valid())
-        self.assert_(form.get_user() is None)
-        self.assertEqual(form.errors.keys(), ['__all__'])
+        self.assertTrue(not form.is_valid())
+        self.assertTrue(form.get_user() is None)
+        self.assertEqual(list(form.errors.keys()), ['__all__'])
 
     def test_no_token(self):
         data = {
         }
         form = OTPAuthenticationForm(None, data)
 
-        self.assert_(not form.is_valid())
-        self.assert_(form.get_user().get_username() == 'alice')
+        self.assertTrue(not form.is_valid())
+        self.assertTrue(form.get_user().get_username() == 'alice')
 
     def test_passive_token(self):
         data = {
         }
         form = OTPAuthenticationForm(None, data)
 
-        self.assert_(form.is_valid())
+        self.assertTrue(form.is_valid())
         alice = form.get_user()
-        self.assert_(alice.get_username() == 'alice')
-        self.assert_(isinstance(alice.otp_device, StaticDevice))
+        self.assertTrue(alice.get_username() == 'alice')
+        self.assertTrue(isinstance(alice.otp_device, StaticDevice))
         self.assertEqual(alice.otp_device.token_set.count(), 2)
 
     def test_spoofed_device(self):
         }
         form = OTPAuthenticationForm(None, data)
 
-        self.assert_(not form.is_valid())
+        self.assertTrue(not form.is_valid())
         alice = form.get_user()
-        self.assert_(alice.get_username() == 'alice')
-        self.assert_(alice.otp_device is None)
+        self.assertTrue(alice.get_username() == 'alice')
+        self.assertTrue(alice.otp_device is None)
 
     def test_specific_device_fail(self):
         data = {
         }
         form = OTPAuthenticationForm(None, data)
 
-        self.assert_(not form.is_valid())
+        self.assertTrue(not form.is_valid())
         alice = form.get_user()
-        self.assert_(alice.get_username() == 'alice')
-        self.assert_(alice.otp_device is None)
+        self.assertTrue(alice.get_username() == 'alice')
+        self.assertTrue(alice.otp_device is None)
 
     def test_specific_device(self):
         data = {
         }
         form = OTPAuthenticationForm(None, data)
 
-        self.assert_(form.is_valid())
+        self.assertTrue(form.is_valid())
         alice = form.get_user()
-        self.assert_(alice.get_username() == 'alice')
-        self.assert_(alice.otp_device is not None)
+        self.assertTrue(alice.get_username() == 'alice')
+        self.assertTrue(alice.otp_device is not None)

django-otp/django_otp/plugins/otp_totp/models.py

         update this any time we match a token that is not the current one.
         (Default: 0)
     """
-    key = models.CharField(max_length=80, validators=[hex_validator()], default=lambda: random_hex(20), help_text=u"A hex-encoded secret key of up to 40 bytes.")
-    step = models.PositiveSmallIntegerField(default=30, help_text=u"The time step in seconds.")
-    t0 = models.BigIntegerField(default=0, help_text=u"The Unix time at which to begin counting steps.")
-    digits = models.PositiveSmallIntegerField(choices=[(6,6), (8,8)], default=6, help_text=u"The number of digits to expect in a token.")
-    tolerance = models.PositiveSmallIntegerField(default=1, help_text=u"The number of time steps in the past or future to allow.")
-    drift = models.SmallIntegerField(default=0, help_text=u"The number of time steps the prover is known to deviate from our clock.")
+    key = models.CharField(max_length=80, validators=[hex_validator()], default=lambda: random_hex(20), help_text="A hex-encoded secret key of up to 40 bytes.")
+    step = models.PositiveSmallIntegerField(default=30, help_text="The time step in seconds.")
+    t0 = models.BigIntegerField(default=0, help_text="The Unix time at which to begin counting steps.")
+    digits = models.PositiveSmallIntegerField(choices=[(6, 6), (8, 8)], default=6, help_text="The number of digits to expect in a token.")
+    tolerance = models.PositiveSmallIntegerField(default=1, help_text="The number of time steps in the past or future to allow.")
+    drift = models.SmallIntegerField(default=0, help_text="The number of time steps the prover is known to deviate from our clock.")
 
     class Meta(Device.Meta):
-        verbose_name = u"TOTP device"
+        verbose_name = "TOTP device"
 
     @property
     def bin_key(self):
         """
         The secret key as a binary string.
         """
-        return unhexlify(self.key)
+        return unhexlify(self.key.encode())
 
     def verify_token(self, token):
         OTP_TOTP_SYNC = getattr(settings, 'OTP_TOTP_SYNC', True)
 
         try:
             token = int(token)
-        except StandardError:
+        except Exception:
             verified = False
         else:
             key = self.bin_key

django-otp/django_otp/plugins/otp_totp/tests.py

         try:
             alice = self.create_user('alice', 'password')
         except IntegrityError:
-            self.skipTest(u"Unable to create the test user.")
+            self.skipTest("Unable to create the test user.")
         else:
             self.device = alice.totpdevice_set.create(
                 key='2a2bbba1092ffdd25a328ad1a0a5f5d61d7aacc4', step=30,
         with self.settings(OTP_TOTP_SYNC=True):
             ok = self.device.verify_token(self.tokens[5])
 
-        self.assert_(ok)
+        self.assertTrue(ok)
         self.assertEqual(self.device.drift, 2)
 
     def test_sync_results(self):

django-otp/django_otp/tests.py

+from doctest import DocTestSuite
+
 import django.test
 from django.utils import unittest
-from doctest import DocTestSuite
 
 from django_otp import util
 from django_otp import oath
 
 
-def suite():
+def load_tests(loader, tests, pattern):
     suite = unittest.TestSuite()
 
+    suite.addTests(tests)
     suite.addTest(DocTestSuite(util))
     suite.addTest(DocTestSuite(oath))
 

django-otp/django_otp/util.py

 from os import urandom
 
 from django.core.exceptions import ValidationError
+from django.utils import six
 
 
 def hex_validator(length=0):
     :rtype: function
 
     >>> hex_validator()('0123456789abcdef')
-    >>> hex_validator(8)('0123456789abcdef')
-    >>> hex_validator()('phlebotinum')
+    >>> hex_validator(8)(b'0123456789abcdef')
+    >>> hex_validator()('phlebotinum')          # doctest: +IGNORE_EXCEPTION_DETAIL
     Traceback (most recent call last):
         ...
-    ValidationError: [u'phlebotinum is not valid hex-encoded data.']
-    >>> hex_validator(9)('0123456789abcdef')
+    ValidationError: ['phlebotinum is not valid hex-encoded data.']
+    >>> hex_validator(9)('0123456789abcdef')    # doctest: +IGNORE_EXCEPTION_DETAIL
     Traceback (most recent call last):
         ...
-    ValidationError: [u'0123456789abcdef does not represent exactly 9 bytes.']
+    ValidationError: ['0123456789abcdef does not represent exactly 9 bytes.']
     """
     def _validator(value):
         try:
+            if isinstance(value, six.text_type):
+                value = value.encode()
+
             unhexlify(value)
-        except StandardError:
-            raise ValidationError(u'{0} is not valid hex-encoded data.'.format(value))
+        except Exception:
+            raise ValidationError('{0} is not valid hex-encoded data.'.format(value))
 
         if (length > 0) and (len(value) != length * 2):
-            raise ValidationError(u'{0} does not represent exactly {1} bytes.'.format(value, length))
+            raise ValidationError('{0} does not represent exactly {1} bytes.'.format(value, length))
 
     return _validator
 
 
 setup(
     name='django-otp',
-    version='0.1.8',
+    version='0.2.0',
     description='A pluggable framework for adding two-factor authentication to Django using one-time passwords.',
     long_description=open('README').read(),
     author='Peter Sagerson',
     url='https://bitbucket.org/psagers/django-otp',
     license='BSD',
     install_requires=[
-        'django >= 1.3'
+        'django >= 1.4'
     ],
     classifiers=[
         "Development Status :: 4 - Beta",
         "Programming Language :: Python :: 2",
+        "Programming Language :: Python :: 2.6",
+        "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.2",
+        "Programming Language :: Python :: 3.3",
         "Intended Audience :: Developers",
         "License :: OSI Approved :: BSD License",
         "Topic :: Security",
         "Topic :: Software Development :: Libraries :: Python Modules",
+        "Framework :: Django",
     ],
 )
+[flake8]
+ignore = E501
+[run]
+data_file = ../../.coverage
+
+source = django_otp
+         otp_agents
+         otp_twilio
+         otp_yubikey
 This folder contains Django projects that are set up specifically for testing
-the django-otp (and related) apps. Some dependencies are expected to be checked
-out in the same directory as django-otp; one or two others will need to be
-installed.
+the django-otp (and related) apps. Use tox to test in a variety of environments
+and generate coverage data.
 
-default_user should pass tests in Django 1.3 and 1.4. custom_user should pass
+default_user should pass tests in all environments. custom_user should pass
 tests in 1.5+. exotic_user should skip most tests in 1.5+.

test/custom_user/.coveragerc

+../.coveragerc

test/default_user/.coveragerc

+../.coveragerc

test/default_user/django_agent_trust

-../../../django-agent-trust/django_agent_trust

test/default_user/yubiotp

-../../../yubiotp/yubiotp

test/exotic_user/.coveragerc

+../.coveragerc
+[tox]
+skipsdist = True
+envlist = py26-django14,
+          py27-django15,
+          py27-django15-custom,
+          py27-django15-exotic,
+          py27-django16,
+          py32-django16,
+          py33-django16,
+
+[testenv]
+basepython = python2.7
+changedir = default_user
+commands = {envbindir}/coverage run -a ./manage.py test django_otp otp_hotp otp_totp otp_static otp_email otp_yubikey otp_twilio otp_agents
+deps = coverage
+       yubiotp
+       requests
+       django-agent-trust
+
+[testenv:py26-django14]
+basepython = python2.6
+deps = {[testenv]deps}
+       django<1.5
+
+[testenv:py27-django15]
+deps = {[testenv]deps}
+       django<1.6
+
+[testenv:py27-django15-custom]
+changedir = custom_user
+commands = {envbindir}/coverage run -a ./manage.py test django_otp otp_hotp otp_totp otp_static otp_email
+deps = coverage
+       django<1.6
+
+[testenv:py27-django15-exotic]
+changedir = exotic_user
+commands = {envbindir}/coverage run -a ./manage.py test django_otp otp_hotp otp_totp otp_static otp_email
+deps = coverage
+       django<1.6
+
+[testenv:py27-django16]
+commands = {envbindir}/coverage run -a ./manage.py test \
+    django_otp \
+    django_otp.plugins.otp_hotp \
+    django_otp.plugins.otp_totp \
+    django_otp.plugins.otp_static \
+    django_otp.plugins.otp_email \
+    otp_yubikey \
+    otp_twilio \
+    otp_agents
+deps = {[testenv]deps}
+       https://www.djangoproject.com/m/releases/1.6/Django-1.6b2.tar.gz
+
+[testenv:py32-django16]
+basepython = python3.2
+commands = {envbindir}/coverage run -a ./manage.py test \
+    django_otp \
+    django_otp.plugins.otp_hotp \
+    django_otp.plugins.otp_totp \
+    django_otp.plugins.otp_static \
+    django_otp.plugins.otp_email \
+    otp_yubikey \
+    otp_twilio \
+    otp_agents
+deps = {[testenv]deps}
+       https://www.djangoproject.com/m/releases/1.6/Django-1.6b2.tar.gz
+
+[testenv:py33-django16]
+basepython = python3.3
+commands = {envbindir}/coverage run -a ./manage.py test \
+    django_otp \
+    django_otp.plugins.otp_hotp \
+    django_otp.plugins.otp_totp \
+    django_otp.plugins.otp_static \
+    django_otp.plugins.otp_email \
+    otp_yubikey \
+    otp_twilio \
+    otp_agents
+deps = {[testenv]deps}
+       https://www.djangoproject.com/m/releases/1.6/Django-1.6b2.tar.gz