[Python-modules-commits] [python-django-otp] 01/09: Import python-django-otp_0.3.10.orig.tar.gz

Michael Fladischer fladi at moszumanska.debian.org
Mon Mar 6 10:15:37 UTC 2017


This is an automated email from the git hooks/post-receive script.

fladi pushed a commit to branch master
in repository python-django-otp.

commit 418274d75dc81df5f55be32a4250e98f3de35c3e
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date:   Mon Mar 6 10:31:57 2017 +0100

    Import python-django-otp_0.3.10.orig.tar.gz
---
 CHANGES                                            | 13 ++++
 MANIFEST.in                                        |  2 +
 PKG-INFO                                           |  3 +-
 django_otp.egg-info/PKG-INFO                       |  3 +-
 django_otp.egg-info/SOURCES.txt                    |  2 +
 django_otp.egg-info/requires.txt                   |  2 +-
 django_otp/plugins/otp_hotp/admin.py               | 71 ++++++++++++++++++
 django_otp/plugins/otp_hotp/models.py              | 31 ++++++++
 .../otp_hotp/templates/otp_hotp/admin/config.html  | 16 ++++
 django_otp/plugins/otp_hotp/tests.py               | 28 +++++++
 .../plugins/otp_static/migrations/0001_initial.py  |  2 +-
 django_otp/plugins/otp_static/models.py            |  2 +-
 django_otp/plugins/otp_totp/admin.py               | 71 ++++++++++++++++++
 django_otp/plugins/otp_totp/models.py              | 30 ++++++++
 .../otp_totp/templates/otp_totp/admin/config.html  | 16 ++++
 django_otp/plugins/otp_totp/tests.py               | 38 +++++++---
 docs/source/conf.py                                |  2 +-
 docs/source/overview.rst                           | 87 +++++++++++++++-------
 setup.py                                           |  5 +-
 19 files changed, 377 insertions(+), 47 deletions(-)

diff --git a/CHANGES b/CHANGES
index de303ef..433eec8 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,16 @@
+v0.3.10 - March 4, 2017 - Built-in QR Code support
+--------------------------------------------------
+
+- `#20`_: Generate HOTP and TOTP otpauth URLs and corresponding QR Codes. You
+  need the `qrcode`_ package installed for this feature.
+
+- Support for Python 2.6 and Django 1.4 were dropped in this version (long
+  overdue).
+
+.. _#20: https://bitbucket.org/psagers/django-otp/issues/20/how-to-pair-from-the-admin
+.. _qrcode: https://pypi.python.org/pypi/qrcode/
+
+
 v0.3.8 - November 27, 2016 - Forward compatbility for Django 2.0
 ----------------------------------------------------------------
 
diff --git a/MANIFEST.in b/MANIFEST.in
index 3caa31a..1f57ad3 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,4 +4,6 @@ recursive-include docs *.rst *.py Makefile
 prune docs/build
 
 recursive-include django_otp/plugins/otp_email/templates *
+recursive-include django_otp/plugins/otp_hotp/templates *
+recursive-include django_otp/plugins/otp_totp/templates *
 recursive-include django_otp/templates *
diff --git a/PKG-INFO b/PKG-INFO
index c4e393f..0bcf992 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: django-otp
-Version: 0.3.8
+Version: 0.3.10
 Summary: A pluggable framework for adding two-factor authentication to Django using one-time passwords.
 Home-page: https://bitbucket.org/psagers/django-otp
 Author: Peter Sagerson
@@ -38,7 +38,6 @@ Classifier: Framework :: Django
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: BSD License
 Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.3
diff --git a/django_otp.egg-info/PKG-INFO b/django_otp.egg-info/PKG-INFO
index c4e393f..0bcf992 100644
--- a/django_otp.egg-info/PKG-INFO
+++ b/django_otp.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: django-otp
-Version: 0.3.8
+Version: 0.3.10
 Summary: A pluggable framework for adding two-factor authentication to Django using one-time passwords.
 Home-page: https://bitbucket.org/psagers/django-otp
 Author: Peter Sagerson
@@ -38,7 +38,6 @@ Classifier: Framework :: Django
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: BSD License
 Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.3
diff --git a/django_otp.egg-info/SOURCES.txt b/django_otp.egg-info/SOURCES.txt
index c20a3ba..a7841df 100644
--- a/django_otp.egg-info/SOURCES.txt
+++ b/django_otp.egg-info/SOURCES.txt
@@ -40,6 +40,7 @@ django_otp/plugins/otp_hotp/migrations/0001_initial.py
 django_otp/plugins/otp_hotp/migrations/__init__.py
 django_otp/plugins/otp_hotp/south_migrations/0001_initial.py
 django_otp/plugins/otp_hotp/south_migrations/__init__.py
+django_otp/plugins/otp_hotp/templates/otp_hotp/admin/config.html
 django_otp/plugins/otp_static/__init__.py
 django_otp/plugins/otp_static/admin.py
 django_otp/plugins/otp_static/lib.py
@@ -61,6 +62,7 @@ django_otp/plugins/otp_totp/migrations/__init__.py
 django_otp/plugins/otp_totp/south_migrations/0001_initial.py
 django_otp/plugins/otp_totp/south_migrations/0002_last_t.py
 django_otp/plugins/otp_totp/south_migrations/__init__.py
+django_otp/plugins/otp_totp/templates/otp_totp/admin/config.html
 django_otp/templates/otp/.DS_Store
 django_otp/templates/otp/admin14/login.html
 django_otp/templates/otp/admin15/login.html
diff --git a/django_otp.egg-info/requires.txt b/django_otp.egg-info/requires.txt
index 5137bb2..7232fe7 100644
--- a/django_otp.egg-info/requires.txt
+++ b/django_otp.egg-info/requires.txt
@@ -1 +1 @@
-django >= 1.4.2
+django >= 1.5.12
diff --git a/django_otp/plugins/otp_hotp/admin.py b/django_otp/plugins/otp_hotp/admin.py
index 49d040a..99be38e 100644
--- a/django_otp/plugins/otp_hotp/admin.py
+++ b/django_otp/plugins/otp_hotp/admin.py
@@ -1,7 +1,12 @@
 from __future__ import absolute_import, division, print_function, unicode_literals
 
+from django.conf.urls import url
 from django.contrib import admin
 from django.contrib.admin.sites import AlreadyRegistered
+from django.core.urlresolvers import reverse
+from django.http import HttpResponse
+from django.template.response import TemplateResponse
+from django.utils.html import format_html
 
 from .models import HOTPDevice
 
@@ -11,6 +16,8 @@ class HOTPDeviceAdmin(admin.ModelAdmin):
     :class:`~django.contrib.admin.ModelAdmin` for
     :class:`~django_otp.plugins.otp_hotp.models.HOTPDevice`.
     """
+    list_display = ['user', 'name', 'confirmed', 'qrcode_link']
+
     fieldsets = [
         ('Identity', {
             'fields': ['user', 'name', 'confirmed'],
@@ -21,10 +28,74 @@ class HOTPDeviceAdmin(admin.ModelAdmin):
         ('State', {
             'fields': ['counter'],
         }),
+        (None, {
+            'fields': ['qrcode_link'],
+        }),
     ]
     raw_id_fields = ['user']
+    readonly_fields = ['qrcode_link']
     radio_fields = {'digits': admin.HORIZONTAL}
 
+    def get_queryset(self, request):
+        queryset = super(HOTPDeviceAdmin, self).get_queryset(request)
+        queryset = queryset.select_related('user')
+
+        return queryset
+
+    #
+    # Columns
+    #
+
+    def qrcode_link(self, device):
+        if device is not None:
+            href = reverse('admin:otp_hotp_hotpdevice_config', kwargs={'pk': device.pk})
+            link = format_html('<a href="{}">qrcode</a>', href)
+        else:
+            link = ''
+
+        return link
+    qrcode_link.short_description = "QR Code"
+
+    #
+    # Custom views
+    #
+
+    def get_urls(self):
+        urls = [
+            url(r'^(?P<pk>\d+)/config/$', self.admin_site.admin_view(self.config_view), name='otp_hotp_hotpdevice_config'),
+            url(r'^(?P<pk>\d+)/qrcode/$', self.admin_site.admin_view(self.qrcode_view), name='otp_hotp_hotpdevice_qrcode'),
+        ] + super(HOTPDeviceAdmin, self).get_urls()
+
+        return urls
+
+    def config_view(self, request, pk):
+        device = HOTPDevice.objects.get(pk=pk)
+
+        try:
+            context = dict(
+                self.admin_site.each_context(request),
+                device=device,
+            )
+        except AttributeError:  # Older versions don't have each_context().
+            context = {'device': device}
+
+        return TemplateResponse(request, 'otp_hotp/admin/config.html', context)
+
+    def qrcode_view(self, request, pk):
+        device = HOTPDevice.objects.get(pk=pk)
+
+        try:
+            import qrcode
+            import qrcode.image.svg
+
+            img = qrcode.make(device.config_url, image_factory=qrcode.image.svg.SvgImage)
+            response = HttpResponse(content_type='image/svg+xml')
+            img.save(response)
+        except ImportError:
+            response = HttpResponse('', status=503)
+
+        return response
+
 
 try:
     admin.site.register(HOTPDevice, HOTPDeviceAdmin)
diff --git a/django_otp/plugins/otp_hotp/models.py b/django_otp/plugins/otp_hotp/models.py
index 8f553fa..ff8b49e 100644
--- a/django_otp/plugins/otp_hotp/models.py
+++ b/django_otp/plugins/otp_hotp/models.py
@@ -1,8 +1,12 @@
 from __future__ import absolute_import, division, print_function, unicode_literals
 
+from base64 import b32encode
 from binascii import unhexlify
 
+from django.conf import settings
 from django.db import models
+from django.utils.six import string_types
+from django.utils.six.moves.urllib.parse import quote, urlencode
 
 from django_otp.models import Device
 from django_otp.oath import hotp
@@ -76,3 +80,30 @@ class HOTPDevice(Device):
                 verified = False
 
         return verified
+
+    @property
+    def config_url(self):
+        """
+        A URL for configuring Google Authenticator or similar.
+
+        See https://github.com/google/google-authenticator/wiki/Key-Uri-Format.
+        The issuer is taken from :setting:`OTP_HOTP_ISSUER`, if available.
+
+        """
+        label = self.user.get_username()
+        params = {
+            'secret': b32encode(self.bin_key),
+            'algorithm': 'SHA1',
+            'digits': self.digits,
+            'counter': self.counter,
+        }
+
+        issuer = getattr(settings, 'OTP_HOTP_ISSUER', None)
+        if isinstance(issuer, string_types) and (issuer != ''):
+            issuer = issuer.replace(':', '')
+            params['issuer'] = issuer
+            label = '{}:{}'.format(issuer, label)
+
+        url = 'otpauth://hotp/{}?{}'.format(quote(label), urlencode(params))
+
+        return url
diff --git a/django_otp/plugins/otp_hotp/templates/otp_hotp/admin/config.html b/django_otp/plugins/otp_hotp/templates/otp_hotp/admin/config.html
new file mode 100644
index 0000000..145d726
--- /dev/null
+++ b/django_otp/plugins/otp_hotp/templates/otp_hotp/admin/config.html
@@ -0,0 +1,16 @@
+{% extends "admin/base_site.html" %}
+
+{% block content %}
+<div style="text-align: center;">
+  <p id="qrcode">
+    <img width="200" height="200"
+         src="{% url "admin:otp_hotp_hotpdevice_qrcode" pk=device.pk %}"
+         onerror="document.getElementById('qrcode').style.display = 'none'; document.getElementById('no-qrcode').style.display = 'block';"
+    >
+  </p>
+  <p id="no-qrcode" style="display: none">
+    Install <a href="https://pypi.python.org/pypi/qrcode/">qrcode</a> or use the URL below:
+  </p>
+  <p>{{ device.config_url }}</p>
+</div>
+{% endblock %}
diff --git a/django_otp/plugins/otp_hotp/tests.py b/django_otp/plugins/otp_hotp/tests.py
index 5660dee..45e5e93 100644
--- a/django_otp/plugins/otp_hotp/tests.py
+++ b/django_otp/plugins/otp_hotp/tests.py
@@ -1,6 +1,8 @@
 from __future__ import absolute_import, division, print_function, unicode_literals
 
 from django.db import IntegrityError
+from django.test.utils import override_settings
+from django.utils.six.moves.urllib.parse import urlsplit, parse_qs
 
 from django_otp.tests import TestCase
 
@@ -43,3 +45,29 @@ class HOTPTest(TestCase):
 
         self.assertTrue(not ok)
         self.assertEqual(self.device.counter, 0)
+
+    def test_config_url_no_issuer(self):
+        with override_settings(OTP_HOTP_ISSUER=None):
+            url = self.device.config_url
+
+        parsed = urlsplit(url)
+        params = parse_qs(parsed.query)
+
+        self.assertEqual(parsed.scheme, 'otpauth')
+        self.assertEqual(parsed.netloc, 'hotp')
+        self.assertEqual(parsed.path, '/alice')
+        self.assertIn('secret', params)
+        self.assertNotIn('issuer', params)
+
+    def test_config_url_issuer(self):
+        with override_settings(OTP_HOTP_ISSUER='example.com'):
+            url = self.device.config_url
+
+        parsed = urlsplit(url)
+        params = parse_qs(parsed.query)
+
+        self.assertEqual(parsed.scheme, 'otpauth')
+        self.assertEqual(parsed.netloc, 'hotp')
+        self.assertEqual(parsed.path, '/example.com%3Aalice')
+        self.assertIn('secret', params)
+        self.assertIn('issuer', params)
diff --git a/django_otp/plugins/otp_static/migrations/0001_initial.py b/django_otp/plugins/otp_static/migrations/0001_initial.py
index cc58165..2368af6 100644
--- a/django_otp/plugins/otp_static/migrations/0001_initial.py
+++ b/django_otp/plugins/otp_static/migrations/0001_initial.py
@@ -30,7 +30,7 @@ class Migration(migrations.Migration):
             fields=[
                 ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                 ('token', models.CharField(max_length=16, db_index=True)),
-                ('device', models.ForeignKey(related_name='token_set', to='otp_static.StaticDevice')),
+                ('device', models.ForeignKey(related_name='token_set', to='otp_static.StaticDevice', on_delete=models.CASCADE)),
             ],
             options={
             },
diff --git a/django_otp/plugins/otp_static/models.py b/django_otp/plugins/otp_static/models.py
index 8a5b075..6069d9e 100644
--- a/django_otp/plugins/otp_static/models.py
+++ b/django_otp/plugins/otp_static/models.py
@@ -45,7 +45,7 @@ class StaticToken(models.Model):
 
         *CharField*: A random string up to 16 characters.
     """
-    device = models.ForeignKey(StaticDevice, related_name='token_set')
+    device = models.ForeignKey(StaticDevice, related_name='token_set', on_delete=models.CASCADE)
     token = models.CharField(max_length=16, db_index=True)
 
     @staticmethod
diff --git a/django_otp/plugins/otp_totp/admin.py b/django_otp/plugins/otp_totp/admin.py
index 2293bb1..003ca91 100644
--- a/django_otp/plugins/otp_totp/admin.py
+++ b/django_otp/plugins/otp_totp/admin.py
@@ -1,7 +1,12 @@
 from __future__ import absolute_import, division, print_function, unicode_literals
 
+from django.conf.urls import url
 from django.contrib import admin
 from django.contrib.admin.sites import AlreadyRegistered
+from django.core.urlresolvers import reverse
+from django.http import HttpResponse
+from django.template.response import TemplateResponse
+from django.utils.html import format_html
 
 from .models import TOTPDevice
 
@@ -11,6 +16,8 @@ class TOTPDeviceAdmin(admin.ModelAdmin):
     :class:`~django.contrib.admin.ModelAdmin` for
     :class:`~django_otp.plugins.otp_totp.models.TOTPDevice`.
     """
+    list_display = ['user', 'name', 'confirmed', 'qrcode_link']
+
     fieldsets = [
         ('Identity', {
             'fields': ['user', 'name', 'confirmed'],
@@ -21,10 +28,74 @@ class TOTPDeviceAdmin(admin.ModelAdmin):
         ('State', {
             'fields': ['drift'],
         }),
+        (None, {
+            'fields': ['qrcode_link'],
+        }),
     ]
     raw_id_fields = ['user']
+    readonly_fields = ['qrcode_link']
     radio_fields = {'digits': admin.HORIZONTAL}
 
+    def get_queryset(self, request):
+        queryset = super(TOTPDeviceAdmin, self).get_queryset(request)
+        queryset = queryset.select_related('user')
+
+        return queryset
+
+    #
+    # Columns
+    #
+
+    def qrcode_link(self, device):
+        if device is not None:
+            href = reverse('admin:otp_totp_totpdevice_config', kwargs={'pk': device.pk})
+            link = format_html('<a href="{}">qrcode</a>', href)
+        else:
+            link = ''
+
+        return link
+    qrcode_link.short_description = "QR Code"
+
+    #
+    # Custom views
+    #
+
+    def get_urls(self):
+        urls = [
+            url(r'^(?P<pk>\d+)/config/$', self.admin_site.admin_view(self.config_view), name='otp_totp_totpdevice_config'),
+            url(r'^(?P<pk>\d+)/qrcode/$', self.admin_site.admin_view(self.qrcode_view), name='otp_totp_totpdevice_qrcode'),
+        ] + super(TOTPDeviceAdmin, self).get_urls()
+
+        return urls
+
+    def config_view(self, request, pk):
+        device = TOTPDevice.objects.get(pk=pk)
+
+        try:
+            context = dict(
+                self.admin_site.each_context(request),
+                device=device,
+            )
+        except AttributeError:  # Older versions don't have each_context().
+            context = {'device': device}
+
+        return TemplateResponse(request, 'otp_totp/admin/config.html', context)
+
+    def qrcode_view(self, request, pk):
+        device = TOTPDevice.objects.get(pk=pk)
+
+        try:
+            import qrcode
+            import qrcode.image.svg
+
+            img = qrcode.make(device.config_url, image_factory=qrcode.image.svg.SvgImage)
+            response = HttpResponse(content_type='image/svg+xml')
+            img.save(response)
+        except ImportError:
+            response = HttpResponse('', status=503)
+
+        return response
+
 
 try:
     admin.site.register(TOTPDevice, TOTPDeviceAdmin)
diff --git a/django_otp/plugins/otp_totp/models.py b/django_otp/plugins/otp_totp/models.py
index e7dbf1b..e001538 100644
--- a/django_otp/plugins/otp_totp/models.py
+++ b/django_otp/plugins/otp_totp/models.py
@@ -1,11 +1,14 @@
 from __future__ import absolute_import, division, print_function, unicode_literals
 
+from base64 import b32encode
 from binascii import unhexlify
 import time
 
 from django.conf import settings
 from django.db import models
 from django.utils.encoding import force_text
+from django.utils.six import string_types
+from django.utils.six.moves.urllib.parse import quote, urlencode
 
 from django_otp.models import Device
 from django_otp.oath import TOTP
@@ -106,3 +109,30 @@ class TOTPDevice(Device):
                 self.save()
 
         return verified
+
+    @property
+    def config_url(self):
+        """
+        A URL for configuring Google Authenticator or similar.
+
+        See https://github.com/google/google-authenticator/wiki/Key-Uri-Format.
+        The issuer is taken from :setting:`OTP_TOTP_ISSUER`, if available.
+
+        """
+        label = self.user.get_username()
+        params = {
+            'secret': b32encode(self.bin_key),
+            'algorithm': 'SHA1',
+            'digits': self.digits,
+            'period': self.step,
+        }
+
+        issuer = getattr(settings, 'OTP_TOTP_ISSUER', None)
+        if isinstance(issuer, string_types) and (issuer != ''):
+            issuer = issuer.replace(':', '')
+            params['issuer'] = issuer
+            label = '{}:{}'.format(issuer, label)
+
+        url = 'otpauth://totp/{}?{}'.format(quote(label), urlencode(params))
+
+        return url
diff --git a/django_otp/plugins/otp_totp/templates/otp_totp/admin/config.html b/django_otp/plugins/otp_totp/templates/otp_totp/admin/config.html
new file mode 100644
index 0000000..3db59b2
--- /dev/null
+++ b/django_otp/plugins/otp_totp/templates/otp_totp/admin/config.html
@@ -0,0 +1,16 @@
+{% extends "admin/base_site.html" %}
+
+{% block content %}
+<div style="text-align: center;">
+  <p id="qrcode">
+    <img width="200" height="200"
+         src="{% url "admin:otp_totp_totpdevice_qrcode" pk=device.pk %}"
+         onerror="document.getElementById('qrcode').style.display = 'none'; document.getElementById('no-qrcode').style.display = 'block';"
+    >
+  </p>
+  <p id="no-qrcode" style="display: none">
+    Install <a href="https://pypi.python.org/pypi/qrcode/">qrcode</a> or use the URL below:
+  </p>
+  <p>{{ device.config_url }}</p>
+</div>
+{% endblock %}
diff --git a/django_otp/plugins/otp_totp/tests.py b/django_otp/plugins/otp_totp/tests.py
index 6804f46..1c7b919 100644
--- a/django_otp/plugins/otp_totp/tests.py
+++ b/django_otp/plugins/otp_totp/tests.py
@@ -3,16 +3,8 @@ from __future__ import absolute_import, division, print_function, unicode_litera
 from time import time
 
 from django.db import IntegrityError
-
-try:
-    from django.test.utils import override_settings
-except ImportError:
-    # Django < 1.4 doesn't have override_settings. Just skip the tests in that
-    # case.
-    from django.utils.unittest import skip
-
-    def override_settings(*args, **kwargs):
-        return skip
+from django.test.utils import override_settings
+from django.utils.six.moves.urllib.parse import urlsplit, parse_qs
 
 from django_otp.tests import TestCase
 
@@ -75,3 +67,29 @@ class TOTPTest(TestCase):
         self.assertEqual(self.device.last_t, 3)
         self.assertTrue(verified1)
         self.assertFalse(verified2)
+
+    def test_config_url(self):
+        with override_settings(OTP_TOTP_ISSUER=None):
+            url = self.device.config_url
+
+        parsed = urlsplit(url)
+        params = parse_qs(parsed.query)
+
+        self.assertEqual(parsed.scheme, 'otpauth')
+        self.assertEqual(parsed.netloc, 'totp')
+        self.assertEqual(parsed.path, '/alice')
+        self.assertIn('secret', params)
+        self.assertNotIn('issuer', params)
+
+    def test_config_url_issuer(self):
+        with override_settings(OTP_TOTP_ISSUER='example.com'):
+            url = self.device.config_url
+
+        parsed = urlsplit(url)
+        params = parse_qs(parsed.query)
+
+        self.assertEqual(parsed.scheme, 'otpauth')
+        self.assertEqual(parsed.netloc, 'totp')
+        self.assertEqual(parsed.path, '/example.com%3Aalice')
+        self.assertIn('secret', params)
+        self.assertIn('issuer', params)
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 24a7a03..6ef1601 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -75,7 +75,7 @@ copyright = u'2012, Peter Sagerson'
 # The short X.Y version.
 version = '0.3'
 # The full version, including alpha/beta/rc tags.
-release = '0.3.8'
+release = '0.3.10'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/docs/source/overview.rst b/docs/source/overview.rst
index fb43b0d..96b26a0 100644
--- a/docs/source/overview.rst
+++ b/docs/source/overview.rst
@@ -172,11 +172,10 @@ are subclassed from :class:`django_otp.models.Device`. Each model class supports
 a single type of OTP device. Remember that when we use the term :term:`device`
 in this context, we're not necessarily referring to a physical device. At the
 code level, a device is a model object that can verify a particular type of OTP.
-For example, you might have a `YubiKey <http://www.yubico.com/yubikey>`_ that
-supports both the Yubico OTP algorithm and the HOTP standard: these would be
-represented as different devices and likely served by different plugins. A
-device that delivered HOTP values to a user by SMS would be a third device
-defined by another plugin.
+For example, you might have a `YubiKey`_ that supports both the Yubico OTP
+algorithm and the HOTP standard: these would be represented as different devices
+and likely served by different plugins. A device that delivered HOTP values to a
+user by SMS would be a third device defined by another plugin.
 
 OTP plugins are distributed as Django apps; to install a plugin, just add it to
 :setting:`INSTALLED_APPS` like any other. The order can be significant: any time
@@ -200,6 +199,9 @@ unconfirmed initially. Unconfirmed devices will be ignored by the high-level OTP
 APIs.
 
 
+.. _YubiKey: http://www.yubico.com/yubikey
+
+
 .. _built-in-plugins:
 
 Built-in Plugins
@@ -209,19 +211,24 @@ django-otp includes support for several standard device types.
 :class:`~django_otp.plugins.otp_hotp.models.HOTPDevice` and
 :class:`~django_otp.plugins.otp_totp.models.TOTPDevice` handle standard OTP
 algorithms, which can be used with a variety of OTP generators. For example,
-it's easy to pair these devices with `Google Authenticator
-<https://github.com/google/google-authenticator>`_ using the `otpauth URL scheme
-<https://github.com/google/google-authenticator/wiki/Key-Uri-Format>`_.
+it's easy to pair these devices with `Google Authenticator`_ using the `otpauth
+URL scheme`_. If you have the `qrcode`_ package installed, the admin interface
+will generate QR Codes for you.
+
+
+.. _Google Authenticator: https://github.com/google/google-authenticator
+.. _otpauth URL scheme: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
+.. _qrcode: https://pypi.python.org/pypi/qrcode/
 
 
 HOTP Devices
 ++++++++++++
 
-`HOTP <http://tools.ietf.org/html/rfc4226#section-5>`_ is an algorithm that
-generates a pseudo-random sequence of codes based on an incrementing counter.
-Every time a prover generates a new code or a verifier verifies one, they
-increment their respective counters. This algorithm will fail if the prover
-generates too many codes without a successful verification.
+`HOTP`_ is an algorithm that generates a pseudo-random sequence of codes based
+on an incrementing counter. Every time a prover generates a new code or a
+verifier verifies one, they increment their respective counters. This algorithm
+will fail if the prover generates too many codes without a successful
+verification.
 
 .. module:: django_otp.plugins.otp_hotp
 
@@ -230,15 +237,28 @@ generates too many codes without a successful verification.
 
 .. autoclass:: django_otp.plugins.otp_hotp.admin.HOTPDeviceAdmin
 
+.. _HOTP: http://tools.ietf.org/html/rfc4226#section-5
+
+HOTP Settings
+'''''''''''''
+
+**OTP_HOTP_ISSUER**
+
+.. setting:: OTP_HOTP_ISSUER
+
+Default: ``None``
+
+The ``issuer`` parameter for the otpauth URL generated by
+:attr:`~django_otp.plugins.otp_hotp.models.HOTPDevice.config_url`.
+
 
 TOTP Devices
 ++++++++++++
 
-`TOTP <http://tools.ietf.org/html/rfc6238#section-4>`_ is an algorithm that
-generates a pseudo-random sequence of codes based on the current time. A typical
-implementation will change codes every 30 seconds, although this is
-configurable. This algorithm will fail if the prover and verifier have clocks
-that drift too far apart.
+`TOTP`_ is an algorithm that generates a pseudo-random sequence of codes based
+on the current time. A typical implementation will change codes every 30
+seconds, although this is configurable. This algorithm will fail if the prover
+and verifier have clocks that drift too far apart.
 
 .. module:: django_otp.plugins.otp_totp
 
@@ -247,9 +267,21 @@ that drift too far apart.
 
 .. autoclass:: django_otp.plugins.otp_totp.admin.TOTPDeviceAdmin
 
+.. _TOTP: http://tools.ietf.org/html/rfc6238#section-4
+
 TOTP Settings
 '''''''''''''
 
+**OTP_TOTP_ISSUER**
+
+.. setting:: OTP_TOTP_ISSUER
+
+Default: ``None``
+
+The ``issuer`` parameter for the otpauth URL generated by
+:attr:`~django_otp.plugins.otp_totp.models.TOTPDevice.config_url`.
+
+
 **OTP_TOTP_SYNC**
 
 .. setting:: OTP_TOTP_SYNC
@@ -300,10 +332,8 @@ Other Plugins
 The framework author also maintains a couple of other plugins for less common
 devices. Third-party plugins are not listed here.
 
-    - `django-otp-yubikey <http://pypi.python.org/pypi/django-otp-yubikey>`_
-      supports YubiKey USB devices.
-    - `django-otp-twilio <http://pypi.python.org/pypi/django-otp-twilio>`_
-      supports delivering OTPs via Twilio's SMS service.
+    - `django-otp-yubikey`_ supports YubiKey USB devices.
+    - `django-otp-twilio`_ supports delivering OTPs via Twilio's SMS service.
 
 
 Settings
@@ -364,7 +394,12 @@ Glossary
 .. rubric:: Footnotes
 
 .. [#agents] If you'd like the second factor to persist across sessions, see
-    `django-agent-trust <http://pypi.python.org/pypi/django-agent-trust>`_ and
-    `django-otp-agents <http://pypi.python.org/pypi/django-otp-agents>`_. The
-    former deals with assigning trust to user agents (i.e. browsers) across
-    sessions and the latter includes tools to use OTPs to establish that trust.
+   `django-agent-trust`_ and `django-otp-agents`_. The former deals with
+   assigning trust to user agents (i.e. browsers) across sessions and the latter
+   includes tools to use OTPs to establish that trust.
+
+
+.. _django-agent-trust: http://pypi.python.org/pypi/django-agent-trust
+.. _django-otp-agents: http://pypi.python.org/pypi/django-otp-agents
+.. _django-otp-yubikey: http://pypi.python.org/pypi/django-otp-yubikey
+.. _django-otp-twilio: http://pypi.python.org/pypi/django-otp-twilio
diff --git a/setup.py b/setup.py
index 3d9978d..8c24d6d 100755
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@ from setuptools import setup, find_packages
 
 setup(
     name='django-otp',
-    version='0.3.8',
+    version='0.3.10',
     description='A pluggable framework for adding two-factor authentication to Django using one-time passwords.',
     long_description=open('README.rst').read(),
     author='Peter Sagerson',
@@ -16,7 +16,7 @@ setup(
     url='https://bitbucket.org/psagers/django-otp',
     license='BSD',
     install_requires=[
-        'django >= 1.4.2'
+        'django >= 1.5.12'
     ],
     classifiers=[
         "Development Status :: 5 - Production/Stable",
@@ -24,7 +24,6 @@ setup(
         "Intended Audience :: Developers",
         "License :: OSI Approved :: BSD License",
         "Programming Language :: Python :: 2",
-        "Programming Language :: Python :: 2.6",
         "Programming Language :: Python :: 2.7",
         "Programming Language :: Python :: 3",
         "Programming Language :: Python :: 3.3",

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-django-otp.git



More information about the Python-modules-commits mailing list