I took part of this conversation offline to spare the list from the gory 
details, but the upshot was that verifying an OTP is logically a mutating 
operation on the verifying device, and thus is not really valid on an unsaved 
model object. I posted an update to the documentation to clarify.

There were a couple of other questions down there that got lost, but they're 
more interesting to Django in general. Managing a heterogenous collection of 
model objects is indeed a little tricky, although it's definitely a solved 
problem: this is precisely what the admin interface does. Technically, it 
should be easy to derive an AdminSite that allows a user to manage their own 
devices, although integrating that cleanly into a broader site design is 
probably more trouble than it's worth. I suspect that your approach using 
ContentTypes is a good one--that's pretty much what they're for.

When I first embarked on this project, I actually thought that device 
management was one of the primary goals. Initially, writing a plugin required 
not just a model class, but a collection of forms that could be used to create 
and manage that device. Eventually I decided that there were several good 
reasons not to go down this road. To take one example, a TOTPDevice is fairly 
generic and could be paired with any number of real-world provers. Google 
Authenticator is the main one I know about, but there are others out there and 
more will emerge. Each prover may have a unique setup flow with different 
required TOTP parameters, some of which can't be anticipated in advance. For 
that reason alone, I think that integrating any kind of management UI directly 
into the device definition is a dangerous layer violation.

I'll back up for a minute and point out that--true to the open-source 
ethic--one reason I haven't published any generic device management code is 
that I haven't myself needed it. I currently have django-otp deployed in two 
places. One is only for internal users ("staff" in Django parlance), so it just 
uses the management interface. The other is targeted at competent but not 
necessarily technical users, so it's fairly directed. For example, I require 
that these users register and confirm an SMS device before I issue backup codes 
and a Google Authenticator key. This all uses device-specific UI, so no need to 
be generic.

This gets at the larger issue, which is designing policies for two-factor 
authentication. The policy that you're proposing is essentially to support as 
many kinds of OTP devices as you can and let users manage their own security. I 
think that model has a lot of merits and I'd be happy to see more sites adopt 
it where appropriate. Of course, the downside to being too casual about 
two-factor policies is account recovery. I recently read an article in which 
Google cited this as their Achilles heel in their push for two-factor 
everywhere. Hence the not-uncommon policy of requiring an SMS backup before 
allowing more volatile options.

What this all comes down to is that I think there's plenty of room for code 
that facilitates different kinds of two-factor authentication policies in 
Django apps. django-otp itself shouldn't include any of them, of course, 
because it's just the framework that provides the underlying mechanisms. I can 
easily imagine a django-otp-authenticator app which has tools for generating 
Google-Authenticator-compatible TOTPDevice objects, rendering QR codes, 
confirming, etc. Or, at a higher level, django-otp-devices, with views that 
allow a user to manage their own heterogenous collection of devices. The 
problem space is quite large and I'm not sure how it all shakes out in the end.

On the decorator question, it occurred to me that I actually use the 
otp-required-if-configured pattern as well, although I had it implemented 
directly in a view hierarchy. There seems to be a bit of incoherence in recent 
Django versions between the decorated-view pattern and the view-as-class 
pattern. In any case, I expanded the otp_required decorator to take an 
if_configured argument. New versions of django-otp and django-otp-agents are 
available.

Thanks so much for your feedback. Do keep in touch either here or privately to 
let me know how it works out.

Peter


On Jul 2, 2013, at 4:42 PM, Jason Arnst-Goodrich <goodri...@gmail.com> wrote:

> I think I found one more nicety to add.
> 
> The following is the verify_token method for TOTPDevice:
> 
>     def verify_token(self, token):
>         OTP_TOTP_SYNC = getattr(settings, 'OTP_TOTP_SYNC', True)
> 
>         try:
>             token = int(token)
>         except StandardError:
>             verified = False
>         else:
>             key = self.bin_key
> 
>             for offset in range(-self.tolerance, self.tolerance + 1):
>                 if totp(key, self.step, self.t0, self.digits, self.drift + 
> offset) == token:
>                     if (offset != 0) and OTP_TOTP_SYNC:
>                         self.drift += offset
>                         self.save()
> 
>                     verified = True
>                     break
>             else:
>                 verified = False
> 
>         return verified
> 
> It some cases it might be nice to verify a token to a device that doesn't 
> actually exist (particularly for validating when users setup 2 factor auth 
> themselves). The easiest way to do this right now is to instantiate a Device 
> and invoke verify_token but if the time is offset it'll want to call save() 
> (which then throws an exception).
> 
> So maybe only call save if it already exists? Or allow some parameter 
> controlling the save on the method? Or maybe provide a separate class method?
> 
> 
> On Monday, July 1, 2013 10:14:35 PM UTC-7, Jason Arnst-Goodrich wrote:
> I'm glad you saw my message - if nothing else just so you know this project 
> is appreciated.
> 
> I've got it working with Google's Authenticator.
> 
> I had initially planned to use another project out there for my OTP needs 
> (there's a small number of them that work 'out of the box') because yours 
> took a little extra effort to hook up.
> 
> I ended up going back and using yours though because it's truly in another 
> class. I have the basics working right now.
> 
> I have a couple of questions - I'm trying to make a self service system for 
> allowing users to enable two factor authentication.
> 
> If I loop django_otp.devices_for_user to allow them to manage their existing 
> devices, It's hard to link to a details page for each device. It might help 
> to have a get_absolute_url() defined on the model (which can be overridden is 
> settings). Right now I'm piping it to a template filter using ContentTypes. 
> TBH, I'm pretty new to dealing with this pattern so I might be thinking about 
> it wrong.
> 
> Lastly, what I'll probably end up doing is building a decorator that 
> basically says "require two factor auth if they have it turned on".
> 
> I guess if I had a wishlist it would be to see a baseline for allowing uses 
> to manage their own OTP devices as well as that decorator built in. I 
> understand it's probably out of the scope of what you have right now. I'd 
> still like like to see something that ties it up nicely. That's basically 
> what I'm building right now except I don't trust my build to work in anyone 
> else's setup - although if I have time I'll see if I can go back and refactor 
> it.
> 
> Anyways, thanks again for the work you've done - it's outstanding.
> 
> On Monday, July 1, 2013 9:26:06 PM UTC-7, Peter Sagerson wrote:
> Thanks, I'm glad you like it. I can look into some kind of demo, although 
> Authenticator support is pretty simple. The documentation already links to 
> Google's URI scheme[1], which has all of the details. All you have to do is 
> create a TOTP or HOTP device (usually the former), encode the key with 
> base32, build a URI as documented, and render a QR code for the user to scan. 
> Alternatively, the user can also type the base32-encoded key in manually. 
> 
> 
> [1] http://code.google.com/p/google-authenticator/wiki/KeyUriFormat 
> [2] https://pypi.python.org/pypi/qrcode 
> 
> 
> On Jun 28, 2013, at 10:23 AM, Jason Arnst-Goodrich <good...@gmail.com> wrote: 
> 
> > I just stumbled on this and it looks absolutely amazing. I do have one 
> > request though: can we get a sample project up that uses Google's 
> > authenticator (or anything else). 
> > 
> > This looks like the best solution for two factor authentication for Django 
> > but I don't think many people will know where to start when it comes to 
> > using it (myself included). 
> > 
> > On Wednesday, September 12, 2012 1:27:26 PM UTC-7, Peter Sagerson wrote: 
> > I recently released a suite of packages to support two-factor 
> > authentication in Django by way of one-time passwords. 
> > 
> > The core package is django-otp, which defines the framework and provides 
> > all of the shared APIs. Integration is possible at several levels, from 
> > low-level APIs (devices_for_user(), match_token(), etc.); to an 
> > AuthenticationForm subclass; to a replacement for Django's login view and 
> > an OTP-enabled admin site. Other niceties include the otp_required 
> > decorator, an analog to login_required. This is not an authentication 
> > backend: although it depends on django.contrib.auth for modeling purposes, 
> > it operates independently of the normal authentication machinery. 
> > 
> > A given user may have zero or more OTP devices against which we can verify 
> > a one-time password. The core project includes Django apps that implement 
> > common devices such as HOTP and TOTP (compatible with Google Authenticator, 
> > among others) and static passwords (typically used as backup codes). The 
> > former include standard features such as tolerance and drift. Separately, 
> > django-otp-yubikey provides support for YubiKey devices (locally or 
> > remotely verified). django-otp-twilio provides support for Twilio's SMS 
> > service for delivering codes by SMS. Implementing support for additional 
> > mechanisms is as simple as subclassing an abstract model class and 
> > implementing a verification method (and optionally a challenge method). Raw 
> > implementations of HOTP and TOTP are provided for convenience along with a 
> > few other generally useful utility functions. 
> > 
> > As a companion to these, I've also released django-agent-trust, which uses 
> > Django 1.4's signed key APIs to tag user-agents that the user has 
> > identified as trustworthy. In other words, this implements the "This is a 
> > private/shared computer" option one often sees on sensitive sites. Features 
> > include revocation and expiration (both absolute and by inactivity; 
> > globally, per-user, and per-agent). django-otp-agents is a project that 
> > glues together django-otp and django-agent-trust to assign trust to 
> > user-agents by way of two-factor authentication (one of the most common 
> > scenarios, it seems). 
> > 
> > Documentation: django-otp, django-otp-yubikey, django-otp-twilio, 
> > django-agent-trust, django-otp-agents 
> > Bitbucket: django-otp, django-agent-trust 
> > 
> > As always, the as-is clause in the BSD license isn't kidding. It's early 
> > days for these yet and while everything has been carefully documented and 
> > unit-tested, not all of the code has had contact with the real world. 
> > Feedback is always welcome. The Google group 
> > https://groups.google.com/forum/#!forum/django-otp is available for 
> > discussion and questions. 
> > 
> > Thanks, 
> > Peter 
> > 
> > -- 
> > You received this message because you are subscribed to a topic in the 
> > Google Groups "Django users" group. 
> > To unsubscribe from this topic, visit 
> > https://groups.google.com/d/topic/django-users/b47ONAEWFos/unsubscribe. 
> > To unsubscribe from this group and all its topics, send an email to 
> > django-users...@googlegroups.com. 
> > To post to this group, send email to django...@googlegroups.com. 
> > Visit this group at http://groups.google.com/group/django-users. 
> > For more options, visit https://groups.google.com/groups/opt_out. 
> >   
> >   
> 
> 
> -- 
> You received this message because you are subscribed to a topic in the Google 
> Groups "Django users" group.
> To unsubscribe from this topic, visit 
> https://groups.google.com/d/topic/django-users/b47ONAEWFos/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to 
> django-users+unsubscr...@googlegroups.com.
> To post to this group, send email to django-users@googlegroups.com.
> Visit this group at http://groups.google.com/group/django-users.
> For more options, visit https://groups.google.com/groups/opt_out.
>  
>  

Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail

Reply via email to