diff options
author | Eli Collins <elic@assurancetechnologies.com> | 2019-11-09 09:16:42 -0500 |
---|---|---|
committer | Eli Collins <elic@assurancetechnologies.com> | 2019-11-09 09:16:42 -0500 |
commit | c3f8a01a0e1a3e669e8a367afa0ed09e0e08f21f (patch) | |
tree | 762de6f236abbc3a00c5142d61bb9837a7f379dc | |
parent | adeee1350fc2c7271873205a503bea6381a861c2 (diff) | |
download | passlib-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.rst | 6 | ||||
-rw-r--r-- | passlib/tests/test_totp.py | 6 | ||||
-rw-r--r-- | passlib/totp.py | 24 |
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""" |