Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for passing an image parameter in the otpauth URL. #123

Merged
merged 1 commit into from May 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/source/overview.rst
Expand Up @@ -315,6 +315,18 @@ The ``issuer`` parameter for the otpauth URL generated by
:attr:`~django_otp.plugins.otp_totp.models.TOTPDevice.config_url`.
This can be a string or a callable to dynamically set the value.

.. setting:: OTP_TOTP_IMAGE

**OTP_TOTP_IMAGE**

Default: ``None``

The ``image`` parameter for the otpauth URL generated by
:attr:`~django_otp.plugins.otp_totp.models.TOTPDevice.config_url`.
It should be a HTTPS URL pointing to a square PNG image.
This will be read and displayed by some authenticator applications, e.g. FreeOTP.
This can be a string or a callable to dynamically set the value.

.. setting:: OTP_TOTP_SYNC

**OTP_TOTP_SYNC**
Expand Down
19 changes: 15 additions & 4 deletions src/django_otp/plugins/otp_totp/models.py
Expand Up @@ -146,6 +146,7 @@ def config_url(self):

See https://github.com/google/google-authenticator/wiki/Key-Uri-Format.
The issuer is taken from :setting:`OTP_TOTP_ISSUER`, if available.
The image (for e.g. FreeOTP) is taken from :setting:`OTP_TOTP_IMAGE`, if available.

"""
label = str(self.user.get_username())
Expand All @@ -157,16 +158,26 @@ def config_url(self):
}
urlencoded_params = urlencode(params)

issuer = getattr(settings, 'OTP_TOTP_ISSUER', None)
if callable(issuer):
issuer = issuer(self)
if isinstance(issuer, str) and (issuer != ''):
issuer = self._read_str_from_settings('OTP_TOTP_ISSUER')
if issuer:
issuer = issuer.replace(':', '')
label = '{}:{}'.format(issuer, label)
urlencoded_params += '&issuer={}'.format(
quote(issuer)
) # encode issuer as per RFC 3986, not quote_plus

image = self._read_str_from_settings('OTP_TOTP_IMAGE')
if image:
urlencoded_params += "&image={}".format(quote(image, safe=':/'))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inclusion of : in the safe characters set is to match the tested logic I'm currently using to append this parameter to the URL string before making a QR code.


url = 'otpauth://totp/{}?{}'.format(quote(label), urlencoded_params)

return url

def _read_str_from_settings(self, key):
val = getattr(settings, key, None)
if callable(val):
val = val(self)
if isinstance(val, str) and (val != ''):
return val
return None
15 changes: 15 additions & 0 deletions src/django_otp/plugins/otp_totp/tests.py
Expand Up @@ -156,6 +156,21 @@ def test_config_url_issuer_method(self):
self.assertIn('issuer', params)
self.assertEqual(params['issuer'][0], 'alice@example.com')

def test_config_url_image(self):
image_url = "https://test.invalid/square.png"

with override_settings(OTP_TOTP_ISSUER=None, OTP_TOTP_IMAGE=image_url):
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.assertEqual(params['image'][0], image_url)


class TOTPAdminTest(TestCase):
def setUp(self):
Expand Down