summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2019-11-09 09:16:42 -0500
committerEli Collins <elic@assurancetechnologies.com>2019-11-09 09:16:42 -0500
commitc3f8a01a0e1a3e669e8a367afa0ed09e0e08f21f (patch)
tree762de6f236abbc3a00c5142d61bb9837a7f379dc
parentadeee1350fc2c7271873205a503bea6381a861c2 (diff)
downloadpasslib-c3f8a01a0e1a3e669e8a367afa0ed09e0e08f21f.tar.gz
bugfix: passlib.totp: always prepend issuer to URIs (fixes issue 92)
For all prior releases of passlib, `TOTP().to_uri()` would only output an "issuer" parameter. Per the KeyURI spec, issuer should also be prepended to the label for backward compatibility.
-rw-r--r--docs/history/1.7.rst6
-rw-r--r--passlib/tests/test_totp.py6
-rw-r--r--passlib/totp.py24
3 files changed, 26 insertions, 10 deletions
diff --git a/docs/history/1.7.rst b/docs/history/1.7.rst
index deb26b0..aa108a5 100644
--- a/docs/history/1.7.rst
+++ b/docs/history/1.7.rst
@@ -14,6 +14,12 @@ Bugfixes
* Python 3.8 compatibility fixes
+* .. py:currentmodule:: passlib.totp
+
+ :mod:`passlib.totp`: The :meth:`TOTP.to_uri` method now prepends the issuer to URI label,
+ (per the KeyURI spec). This should fix some compatibility issues with older TOTP clients
+ (:issue:`92`)
+
* **unittests**: ``crypt()`` unittests now account for linux systems running libxcrypt
(such as recent Fedora releases)
diff --git a/passlib/tests/test_totp.py b/passlib/tests/test_totp.py
index 1229da7..d994d6c 100644
--- a/passlib/tests/test_totp.py
+++ b/passlib/tests/test_totp.py
@@ -1372,7 +1372,7 @@ class TotpTest(TestCase):
# with label & issuer
otp = TOTP(KEY4, alg="sha1", digits=6, period=30)
self.assertEqual(otp.to_uri("alice@google.com", "Example Org"),
- "otpauth://totp/alice@google.com?secret=JBSWY3DPEHPK3PXP&"
+ "otpauth://totp/Example%20Org:alice@google.com?secret=JBSWY3DPEHPK3PXP&"
"issuer=Example%20Org")
# label is required
@@ -1390,13 +1390,13 @@ class TotpTest(TestCase):
# with default label & default issuer from constructor
otp.issuer = "Example Org"
self.assertEqual(otp.to_uri(),
- "otpauth://totp/alice@google.com?secret=JBSWY3DPEHPK3PXP"
+ "otpauth://totp/Example%20Org:alice@google.com?secret=JBSWY3DPEHPK3PXP"
"&issuer=Example%20Org")
# reject invalid label
self.assertRaises(ValueError, otp.to_uri, "label:with:semicolons")
- # reject invalid issue
+ # reject invalid issuer
self.assertRaises(ValueError, otp.to_uri, "alice@google.com", "issuer:with:semicolons")
#-------------------------------------------------------------------------
diff --git a/passlib/totp.py b/passlib/totp.py
index dcbd709..7d8c6ed 100644
--- a/passlib/totp.py
+++ b/passlib/totp.py
@@ -1413,11 +1413,12 @@ class TOTP(object):
if ":" in label:
try:
issuer, label = label.split(":")
- except ValueError: # too many ":"
+ except ValueError: # too many ":"
raise cls._uri_parse_error("malformed label")
else:
issuer = None
if label:
+ # NOTE: KeyURI spec says there may be leading spaces
label = label.strip() or None
# parse query params
@@ -1517,6 +1518,11 @@ class TOTP(object):
>>> uri = tp.to_uri("user@example.org", "myservice.another-example.org")
>>> uri
'otpauth://totp/user@example.org?secret=S3JDVB7QD2R7JPXX&issuer=myservice.another-example.org'
+
+ .. versionchanged:: 1.7.2
+
+ This method now prepends the issuer URI label. This is recommended by the KeyURI
+ specification, for compatibility with older clients.
"""
# encode label
if label is None:
@@ -1530,21 +1536,25 @@ class TOTP(object):
label = quote(label, '@')
# encode query parameters
- args = self._to_uri_params()
+ params = self._to_uri_params()
if issuer is None:
issuer = self.issuer
if issuer:
self._check_issuer(issuer)
- args.append(("issuer", issuer))
+ # NOTE: per KeyURI spec, including issuer as part of label is deprecated,
+ # in favor of adding it to query params. however, some QRCode clients
+ # don't recognize the 'issuer' query parameter, so spec recommends (as of 2018-7)
+ # to include both.
+ label = "%s:%s" % (quote(issuer, '@'), label)
+ params.append(("issuer", issuer))
# NOTE: not using urllib.urlencode() because it encodes ' ' as '+';
# but spec says to use '%20', and not sure how fragile
# the various totp clients' parsers are.
- argstr = u("&").join(u("%s=%s") % (key, quote(value, ''))
- for key, value in args)
- assert argstr, "argstr should never be empty"
+ param_str = u("&").join(u("%s=%s") % (key, quote(value, '')) for key, value in params)
+ assert param_str, "param_str should never be empty"
# render uri
- return u("otpauth://totp/%s?%s") % (label, argstr)
+ return u("otpauth://totp/%s?%s") % (label, param_str)
def _to_uri_params(self):
"""return list of (key, param) entries for URI"""