diff options
author | Eli Collins <elic@assurancetechnologies.com> | 2016-11-07 20:30:55 -0500 |
---|---|---|
committer | Eli Collins <elic@assurancetechnologies.com> | 2016-11-07 20:30:55 -0500 |
commit | d5566625e9ef818665c5a7f73ce7c5872e2b2879 (patch) | |
tree | 88f7a0cbfd0ce556c8df780f215b24642f036d1b | |
parent | 63f952a7c9b0e306a8116e21cab9d8da6b811d6a (diff) | |
download | passlib-d5566625e9ef818665c5a7f73ce7c5872e2b2879.tar.gz |
totp: stripped out BaseOTP subtype resolution
In preparation for merging BaseOTP & TOTP:
* removed BaseOTP.type attr & ._type_map,
* hardcoded TOTP class a few places since _type_map is gone
* removed 'type' parameter from dict/json format (not needed since HTOP gone)
* replaced refs to BaseOTP.from_*()/to_*() with TOTP.from_*()/to_*(),
since the base class methods no longer work.
-rw-r--r-- | docs/lib/passlib.totp-tutorial.rst | 20 | ||||
-rw-r--r-- | docs/lib/passlib.totp.rst | 12 | ||||
-rw-r--r-- | passlib/totp.py | 75 |
3 files changed, 44 insertions, 63 deletions
diff --git a/docs/lib/passlib.totp-tutorial.rst b/docs/lib/passlib.totp-tutorial.rst index 4a56088..a840d08 100644 --- a/docs/lib/passlib.totp-tutorial.rst +++ b/docs/lib/passlib.totp-tutorial.rst @@ -83,7 +83,7 @@ TOTP applications support the user grabbing this configuration off the screen. When transferring things this way, you will need to provide identifiers for your application and the user, in order for the TOTP client to distinguish this key from the others in it's database. This is done via the "issue" and "label" -parameters of the :meth:`~passlib.totp.BaseOTP.to_uri` method: +parameters of the :meth:`~passlib.totp.TOTP.to_uri` method: >>> # assume an existing TOTP instance has been created >>> from passlib import totp @@ -117,8 +117,8 @@ URI. This can be useful for testing URI encoding & output: .. seealso:: - For more details, see the :meth:`~passlib.totp.BaseOTP.from_uri` constructor, - and the :meth:`~passlib.totp.BaseOTP.to_uri` method. + For more details, see the :meth:`~passlib.totp.TOTP.from_uri` constructor, + and the :meth:`~passlib.totp.TOTP.to_uri` method. Storing TOTP instances ---------------------- @@ -126,7 +126,7 @@ One disadvantage of :meth:`~TOTP.to_uri` and :func:`!from_uri` (above) is that they're oriented towards helping a server configure a client device. Server applications will still need to persist this information to disk (whether a database, flat file, etc). To help with this, passlib offers -a way to serialize OTP tokens to and from JSON: the :meth:`BaseOTP.to_json` method, +a way to serialize OTP tokens to and from JSON: the :meth:`TOTP.to_json` method, and the :meth:`passlib.totp.from_uri` constructor:: >>> # assume an existing TOTP instance has been created @@ -153,15 +153,15 @@ they can include stateful data (see :ref:`totp-stateful-usage` below), and they support storing the keys in an encrypted fashion (see :ref:`totp-context-usage` below). For cases where a python dictionary is more useful than a json string, -the :meth:`BaseOTP.to_dict` method returns a python dict identical to parsing -the output of :meth:`BaseOTP.to_json`. This value can be reconstructed +the :meth:`TOTP.to_dict` method returns a python dict identical to parsing +the output of :meth:`TOTP.to_json`. This value can be reconstructed via :func:`from_json`, which will autodetect whether the input is a dictionary vs string. .. seealso:: - For more details, see the :meth:`~passlib.totp.BaseOTP.from_json` constructor, - and the :meth:`~passlib.totp.BaseOTP.to_json` method; + For more details, see the :meth:`~passlib.totp.TOTP.from_json` constructor, + and the :meth:`~passlib.totp.TOTP.to_json` method; as well as the stateful usage & OTPContext usage tutorials below. Generating Tokens @@ -337,7 +337,7 @@ if an attacker then comes along and attempts to re-use this token within :meth:` .. seealso:: - The :meth:`TOTP.consume` and :meth:`BaseOTP.to_json` methods, + The :meth:`TOTP.consume` and :meth:`TOTP.to_json` methods, the :attr:`TOTP.last_counter` attribute, and the :func:`~totp.from_json` constructor. .. @@ -363,7 +363,7 @@ application, using passlib's highlevel :class:`OTPContext` helper class. Why Rate Limiting is Critical ============================= -The :meth:`HOTP.verify` and :meth:`TOTP.verify` methods both offer a ``window`` +The :meth:`TOTP.verify` methods offers a ``window`` parameter, expanding the search range to account for the client getting slightly out of sync. diff --git a/docs/lib/passlib.totp.rst b/docs/lib/passlib.totp.rst index 8e5c9d6..ef29572 100644 --- a/docs/lib/passlib.totp.rst +++ b/docs/lib/passlib.totp.rst @@ -56,7 +56,7 @@ can be performed with the following methods: Boolean flag set by all BaseOTP subclass methods which modify the internal state. if true, then something has changed in the object since it was created / loaded - via :meth:`~BaseOTP.from_json`, and needs re-persisting via :meth:`~BaseOTP.to_json`. + via :meth:`~TOTP.from_json`, and needs re-persisting via :meth:`~TOTP.to_json`. After which, your application may clear the flag, or discard the object, as appropriate. .. automethod:: BaseOTP.to_json @@ -75,7 +75,7 @@ BaseOTP – Configuration Attributes ---------------------------------- All the OTP objects offer the following attributes, which correspond to the constructor options (above). -Most of this information will be serialized by :meth:`~BaseOTP.to_uri` and :meth:`~BaseOTP.to_json`: +Most of this information will be serialized by :meth:`~TOTP.to_uri` and :meth:`~TOTP.to_json`: .. autoattribute:: BaseOTP.key .. autoattribute:: BaseOTP.hex_key @@ -136,7 +136,7 @@ this class also offers the following extra attrs (which correspond to the extra TOTP – Internal State Attributes -------------------------------- The following attributes are used to track the internal state of this generator, -and will be included in the output of :meth:`~BaseOTP.to_json`: +and will be included in the output of :meth:`~TOTP.to_json`: .. autoattribute:: TOTP.last_counter @@ -166,7 +166,7 @@ Support Functions .. function:: from_uri(uri) Create an TOTP instance from a provisioning URI, - such as generated by :meth:`BaseOTP.to_uri`. + such as generated by :meth:`TOTP.to_uri`. :returns: :class:`TOTP` instance @@ -174,9 +174,9 @@ Support Functions .. function:: from_json(json) Create an TOTP instance from a JSON string, - such as generated by :meth:`BaseOTP.to_json`. + such as generated by :meth:`TOTP.to_json`. Also accepts a dict object with the same format, - such as returned by :meth:`BaseOTP.to_dict`. + such as returned by :meth:`TOTP.to_dict`. :returns: :class:`TOTP` instance diff --git a/passlib/totp.py b/passlib/totp.py index 6e6270a..8e93223 100644 --- a/passlib/totp.py +++ b/passlib/totp.py @@ -386,55 +386,51 @@ class OTPContext(object): #======================================================================== # frontend wrappers #======================================================================== - def new(self, type="totp", **kwds): + def new(self, **kwds): """ Create new OTP instance from scratch, generating a new key. - :param type: - "totp" (the default) - :param \*\*kwds: All remaining keywords passed to the :class:`TOTP` constructor. :return: :class:`!TOTP` instance. """ - cls = BaseOTP._type_map[type] - return cls(new=True, context=self, **kwds) + return TOTP(new=True, context=self, **kwds) def from_uri(self, uri): """ Create OTP instance from configuration uri. - This is just a wrapper for :meth:`BaseOTP.from_uri` + This is just a wrapper for :meth:`TOTP.from_uri` which returns an OTP object tied to this context (and will thus use any application secrets to encrypt the key for storage). :param uri: URI to parse. This URI may come externally (e.g. from a scanned qrcode), - or from the :meth:`BaseOTP.to_uri` method. + or from the :meth:`TOTP.to_uri` method. :return: :class:`TOTP` instance. """ - return BaseOTP.from_uri(uri, context=self) + return TOTP.from_uri(uri, context=self) def from_json(self, source): """ Create OTP instance from serialized json state. - This is just a wrapper for :class:`BaseOTP.from_json`, + This is just a wrapper for :meth:`TOTP.from_json`, and returns an OTP object tied to this context. :param source: - json string as returned by :class:`BaseOTP.to_json`. + json string as returned by :meth:`TOTP.to_json`. :return: :class:`TOTP` instance. """ - return BaseOTP.from_json(source, context=self) + return TOTP.from_json(source, context=self) #======================================================================== # encrypted key helpers -- used internally by BaseOTP @@ -644,17 +640,10 @@ class BaseOTP(object): # class attrs #============================================================================= - #: otpauth uri type that subclass implements ('totp' or 'hotp') - #: (used by uri & serialization code) - type = None - #: minimum number of bytes to allow in key, enforced by passlib. # XXX: see if spec says anything relevant to this. _min_key_size = 10 - #: dict used by from_uri() to lookup subclass based on otpauth type - _type_map = {} - #: minimum & current serialization version (may be set independently by subclasses) min_json_version = json_version = 1 @@ -969,12 +958,17 @@ class BaseOTP(object): if result.scheme != "otpauth": raise cls._uri_error("wrong uri scheme") - # lookup factory to handle OTP type, and hand things off to it. - try: - subcls = cls._type_map[result.netloc] - except KeyError: - raise cls._uri_error("unknown OTP type") - return subcls._from_parsed_uri(result, context) + # validate netloc, and hand off to helper + cls._check_otp_type(result.netloc) + return cls._from_parsed_uri(result, context) + + @classmethod + def _check_otp_type(cls, type): + if type == "totp": + return True + if type == "hotp": + raise NotImplementedError("HOTP not supported") + raise ValueError("unknown otp type: %r" % type) @classmethod def _from_parsed_uri(cls, result, context): @@ -1050,7 +1044,7 @@ class BaseOTP(object): @classmethod def _uri_error(cls, reason): """uri parsing helper -- creates preformatted error message""" - prefix = cls.__name__ + ": " if cls.type else "" + prefix = cls.__name__ + ": " return ValueError("%sInvalid otpauth uri: %s" % (prefix, reason)) @classmethod @@ -1139,7 +1133,7 @@ class BaseOTP(object): assert argstr, "argstr should never be empty" # render uri - return u("otpauth://%s/%s?%s") % (self.type, label, argstr) + return u("otpauth://totp/%s?%s") % (label, argstr) def _to_uri_params(self): """return list of (key, param) entries for URI""" @@ -1184,16 +1178,12 @@ class BaseOTP(object): return cls.from_uri(source, context=context) else: source = json.loads(source) - if not (isinstance(source, dict) and "type" in source): + if not isinstance(source, dict): raise cls._json_error("unrecognized json data") - try: - subcls = cls._type_map[source.pop('type')] - except KeyError: - raise cls._json_error("unknown OTP type") - return subcls(context=context, **subcls._adapt_json_dict(**source)) + return cls(context=context, **cls._adapt_json_dict(**source)) @classmethod - def _adapt_json_dict(cls, **kwds): + def _adapt_json_dict(cls, type="totp", **kwds): """ Internal helper for .from_json() -- Adapts serialized json dict into constructor keywords. @@ -1201,6 +1191,7 @@ class BaseOTP(object): # default json format is just serialization of constructor kwds. # XXX: just pass all this through to _from_json / constructor? # go ahead and mark as changed (needs re-saving) if the version is too old + assert cls._check_otp_type(type), "check legacy type parameter" ver = kwds.pop("v", None) if not ver or ver < cls.min_json_version or ver > cls.json_version: raise cls._json_error("missing/unsupported version (%r)" % (ver,)) @@ -1221,8 +1212,8 @@ class BaseOTP(object): @classmethod def _json_error(cls, reason): """json parsing helper -- creates preformatted error message""" - prefix = cls.__name__ + ": " if cls.type else "" - return ValueError("%sInvalid otp json string: %s" % (prefix, reason)) + prefix = cls.__name__ + ": " + return ValueError("%sInvalid totp json data: %s" % (prefix, reason)) #============================================================================= # json rendering @@ -1256,7 +1247,7 @@ class BaseOTP(object): :returns: dictionary, containing basic (json serializable) datatypes. """ - state = dict(type=self.type, v=self.json_version) + state = dict(v=self.json_version) if self.alg != "sha1": state['alg'] = self.alg if self.digits != 6: @@ -1434,13 +1425,6 @@ class TOTP(BaseOTP): See the passlib documentation for a full list of attributes & methods. """ #============================================================================= - # class attrs - #============================================================================= - - #: otpauth type this class implements - type = "totp" - - #============================================================================= # instance attrs #============================================================================= @@ -1899,9 +1883,6 @@ class TOTP(BaseOTP): # eoc #============================================================================= -# register subclass with from_uri() helper -BaseOTP._type_map[TOTP.type] = TOTP - #============================================================================= # convenience helpers #============================================================================= |