auth2_oath: comletely remove this module, as it does not depend entirely on Entr'ouvert copyright
We will recreate it as an external plugin.
This commit is contained in:
parent
b67842207b
commit
493c89eb6b
2
COPYING
2
COPYING
|
@ -663,7 +663,5 @@ if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
<http://www.gnu.org/licenses/>.
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
External modules oath and totp-js modules are licensed under a BSD-like licence.
|
|
||||||
|
|
||||||
OpenID idp module is derived of the project django_openid_provider which is
|
OpenID idp module is derived of the project django_openid_provider which is
|
||||||
distributed under the Apache 2.0 license.
|
distributed under the Apache 2.0 license.
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.db import transaction
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from authentic2.compat import get_user_model
|
|
||||||
import authentic2.vendor.oath.hotp as hotp
|
|
||||||
from authentic2.nonce import accept_nonce
|
|
||||||
import models
|
|
||||||
logger = logging.getLogger('authentic.auth.auth2_oath')
|
|
||||||
|
|
||||||
NONCE_TIMEOUT = getattr(settings, 'OATH_NONCE_TIMEOUT',
|
|
||||||
getattr(settings, 'NONCE_TIMEOUT', 3600))
|
|
||||||
|
|
||||||
class OATHTOTPBackend:
|
|
||||||
supports_object_permissions = False
|
|
||||||
supports_anonymous_user = False
|
|
||||||
|
|
||||||
@transaction.commit_on_success()
|
|
||||||
def authenticate(self, username, oath_otp, format='dec6'):
|
|
||||||
'''Lookup the TOTP or HOTP secret for the user and try to authenticate
|
|
||||||
the proposed OTP using it.
|
|
||||||
'''
|
|
||||||
User = get_user_model()
|
|
||||||
try:
|
|
||||||
secret = models.OATHTOTPSecret.objects.get(user__username=username)
|
|
||||||
except models.OATHTOTPSecret.DoesNotExist:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
accepted, drift = hotp.accept_totp(secret.key, oath_otp, format=format)
|
|
||||||
except Exception, e:
|
|
||||||
logger.exception('hotp.accept_totp raised', e)
|
|
||||||
raise
|
|
||||||
|
|
||||||
if accepted:
|
|
||||||
if not accept_nonce(oath_otp, 'OATH_OTP', NONCE_TIMEOUT):
|
|
||||||
logger.error('already used OTP %r', oath_otp)
|
|
||||||
return None
|
|
||||||
|
|
||||||
secret.drift = drift
|
|
||||||
return User.objects.get(username=username)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_user(self, user_id):
|
|
||||||
"""
|
|
||||||
simply return the user object. That way, we only need top look-up the
|
|
||||||
certificate once, when loggin in
|
|
||||||
"""
|
|
||||||
User = get_user_model()
|
|
||||||
try:
|
|
||||||
return User.objects.get(id=user_id)
|
|
||||||
except User.DoesNotExist:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def setup_totp(self, user):
|
|
||||||
'''Create a model containing a TOTP secret for the given user and the
|
|
||||||
current time drift which initially is zero
|
|
||||||
'''
|
|
||||||
pass
|
|
|
@ -1,64 +0,0 @@
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from django.utils.translation import gettext_noop
|
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
|
||||||
from django.contrib.auth import login, authenticate
|
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
import django.forms as forms
|
|
||||||
|
|
||||||
import authentic2.auth2_auth.models as models
|
|
||||||
import views
|
|
||||||
|
|
||||||
# Only difference with login/password form is the user of 'otp' intead of
|
|
||||||
# password as an argument to the authenticate() method
|
|
||||||
# So you need an OTP enabled backend to this to work
|
|
||||||
class OATHOTPHAuthenticationForm(AuthenticationForm):
|
|
||||||
def clean(self):
|
|
||||||
username = self.cleaned_data.get('username')
|
|
||||||
password = self.cleaned_data.get('password')
|
|
||||||
|
|
||||||
if username and password:
|
|
||||||
self.user_cache = authenticate(username=username, oath_otp=password)
|
|
||||||
if self.user_cache is None:
|
|
||||||
raise forms.ValidationError(_("Please enter a correct username and one-time password. Note that both fields are case-sensitive."))
|
|
||||||
elif not self.user_cache.is_active:
|
|
||||||
raise forms.ValidationError(_("This account is inactive."))
|
|
||||||
|
|
||||||
# TODO: determine whether this should move to its own method.
|
|
||||||
if self.request:
|
|
||||||
if not self.request.session.test_cookie_worked():
|
|
||||||
raise forms.ValidationError(_("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in."))
|
|
||||||
|
|
||||||
return self.cleaned_data
|
|
||||||
|
|
||||||
|
|
||||||
class OATHOTPFrontend(object):
|
|
||||||
def enabled(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def name(self):
|
|
||||||
return gettext_noop('One time password')
|
|
||||||
|
|
||||||
def id(self):
|
|
||||||
return 'oath-otp'
|
|
||||||
|
|
||||||
def form(self):
|
|
||||||
return OATHOTPHAuthenticationForm
|
|
||||||
|
|
||||||
def post(self, request, form, nonce, next):
|
|
||||||
# Login the user
|
|
||||||
login(request, form.get_user())
|
|
||||||
# Keep a trace
|
|
||||||
if 'HTTPS' in request.environ.get('HTTPS','').lower() == 'on':
|
|
||||||
how = 'oath-totp-on-https'
|
|
||||||
else:
|
|
||||||
how = 'oath-totp'
|
|
||||||
if nonce:
|
|
||||||
models.AuthenticationEvent(who=form.get_user().username, how=how,
|
|
||||||
nonce=nonce).save()
|
|
||||||
return HttpResponseRedirect(next)
|
|
||||||
|
|
||||||
def profile(self, request, next=''):
|
|
||||||
return views.totp_profile(request, next)
|
|
||||||
|
|
||||||
def template(self):
|
|
||||||
return 'auth/login_form_oath.html'
|
|
|
@ -1,120 +0,0 @@
|
||||||
# French translation of Authentic
|
|
||||||
# Copyright (C) 2010, 2011 Entr'ouvert
|
|
||||||
# This file is distributed under the same license as the Authentic package.
|
|
||||||
# Frederic Peters <fpeters@entrouvert.com>, 2010.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: Authentic\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2013-07-23 17:44+0200\n"
|
|
||||||
"PO-Revision-Date: 2013-07-23 17:42+0200\n"
|
|
||||||
"Last-Translator: Mikaël Ates <mates@entrouvert.com>\n"
|
|
||||||
"Language-Team: None\n"
|
|
||||||
"Language: fr\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=n>1;\n"
|
|
||||||
|
|
||||||
#: frontend.py:22
|
|
||||||
msgid ""
|
|
||||||
"Please enter a correct username and one-time password. Note that both fields "
|
|
||||||
"are case-sensitive."
|
|
||||||
msgstr ""
|
|
||||||
"Veuillez taper un nom d'utilisateur correct et un mot de passe à usage "
|
|
||||||
"unique. Notez que les deux champs sont sensibles à la casse."
|
|
||||||
|
|
||||||
#: frontend.py:24
|
|
||||||
msgid "This account is inactive."
|
|
||||||
msgstr "Ce compte est inactif."
|
|
||||||
|
|
||||||
#: frontend.py:29
|
|
||||||
msgid ""
|
|
||||||
"Your Web browser doesn't appear to have cookies enabled. Cookies are "
|
|
||||||
"required for logging in."
|
|
||||||
msgstr ""
|
|
||||||
"Il semblerait que votre navigateur ne supporte pas les cookies. Les cookies "
|
|
||||||
"sont requis pour se connecter."
|
|
||||||
|
|
||||||
#: frontend.py:39
|
|
||||||
msgid "One time password"
|
|
||||||
msgstr "Mot de passe à usage unique"
|
|
||||||
|
|
||||||
#: templates/auth/login_form_oath.html:5
|
|
||||||
msgid ""
|
|
||||||
"\n"
|
|
||||||
"Once you have created your account, log in with an other authentication "
|
|
||||||
"method.\n"
|
|
||||||
"Then, in account management, follow the instructions to deploy the\n"
|
|
||||||
"One Time password authentication method.\n"
|
|
||||||
msgstr ""
|
|
||||||
"\n"
|
|
||||||
"Une fois votre compte créé, connectez-vous à l'aide d'une autre méthode.\n"
|
|
||||||
"Alors, dans l'interface de gestion de votre compte, suivez les instructions "
|
|
||||||
"pour déployer la\n"
|
|
||||||
"méthode d'authentification basée sur le mot de passe à usage unique.\n"
|
|
||||||
|
|
||||||
#: templates/auth/login_form_oath.html:16
|
|
||||||
msgid "Log in"
|
|
||||||
msgstr "S'identifier"
|
|
||||||
|
|
||||||
#: templates/auth/login_form_oath.html:18
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr "Annuler"
|
|
||||||
|
|
||||||
#: templates/auth/login_form_oath.html:24
|
|
||||||
msgid "Forgot password?"
|
|
||||||
msgstr "Mot de passe oublié ?"
|
|
||||||
|
|
||||||
#: templates/auth/login_form_oath.html:24
|
|
||||||
msgid "Reset it!"
|
|
||||||
msgstr "Le réinitialiser !"
|
|
||||||
|
|
||||||
#: templates/auth/login_form_oath.html:25
|
|
||||||
msgid "Not a member?"
|
|
||||||
msgstr "Pas un membre ?"
|
|
||||||
|
|
||||||
#: templates/auth/login_form_oath.html:25
|
|
||||||
msgid "Register!"
|
|
||||||
msgstr "S'inscrire !"
|
|
||||||
|
|
||||||
#: templates/oath/totp_profile.html:5
|
|
||||||
msgid "Time based one-time password"
|
|
||||||
msgstr "Mot de passe à usage unique basé sur le temps"
|
|
||||||
|
|
||||||
#: templates/oath/totp_profile.html:8
|
|
||||||
msgid "Secret"
|
|
||||||
msgstr "Secret"
|
|
||||||
|
|
||||||
#: templates/oath/totp_profile.html:10
|
|
||||||
msgid "Google authenticator"
|
|
||||||
msgstr "Google authenticator"
|
|
||||||
|
|
||||||
#: templates/oath/totp_profile.html:12
|
|
||||||
msgid "Bookmarklet"
|
|
||||||
msgstr "Marque-page générateur de mot de passe"
|
|
||||||
|
|
||||||
#: templates/oath/totp_profile.html:13
|
|
||||||
msgid ""
|
|
||||||
"Copy this link to your bookmarks. When clicking on it it will generate a new "
|
|
||||||
"one-time password which will allow you to login"
|
|
||||||
msgstr ""
|
|
||||||
"Copier ce lien dans vos marque-pages. Cliquez le pour générer un nouveau mot "
|
|
||||||
"de passe à usage unique pour vous connecter."
|
|
||||||
|
|
||||||
#: templates/oath/totp_profile.html:16
|
|
||||||
msgid "Delete this credential"
|
|
||||||
msgstr "Supprimer ce moyen d'authentification"
|
|
||||||
|
|
||||||
#: templates/oath/totp_profile.html:19
|
|
||||||
msgid ""
|
|
||||||
"This kind of authentication is actually not possible, because you do not "
|
|
||||||
"have any secret setup."
|
|
||||||
msgstr ""
|
|
||||||
"Ce moyen d'authentification n'est pas disponible actuellement car vous "
|
|
||||||
"n'avez pas encore généré de secret."
|
|
||||||
|
|
||||||
#: templates/oath/totp_profile.html:23
|
|
||||||
msgid "Generate a new credential"
|
|
||||||
msgstr "Générer un nouveau secret"
|
|
|
@ -1,39 +0,0 @@
|
||||||
# encoding: utf-8
|
|
||||||
from south.db import db
|
|
||||||
from south.v2 import SchemaMigration
|
|
||||||
|
|
||||||
|
|
||||||
from authentic.compat import user_model_label
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(SchemaMigration):
|
|
||||||
|
|
||||||
def forwards(self, orm):
|
|
||||||
# Adding model 'OATHTOTPSecret'
|
|
||||||
db.create_table('auth2_oath_oathtotpsecret', (
|
|
||||||
('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='oath_totp_secret', unique=True, primary_key=True, to=orm[user_model_label])),
|
|
||||||
('key', self.gf('django.db.models.fields.CharField')(max_length=40)),
|
|
||||||
('drift', self.gf('django.db.models.fields.IntegerField')(default=0, max_length=4)),
|
|
||||||
))
|
|
||||||
db.send_create_signal('auth2_oath', ['OATHTOTPSecret'])
|
|
||||||
|
|
||||||
|
|
||||||
def backwards(self, orm):
|
|
||||||
# Deleting model 'OATHTOTPSecret'
|
|
||||||
db.delete_table('auth2_oath_oathtotpsecret')
|
|
||||||
|
|
||||||
|
|
||||||
models = {
|
|
||||||
user_model_label: {
|
|
||||||
'Meta': {'object_name': user_model_label.split('.')[-1]},
|
|
||||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
|
||||||
},
|
|
||||||
'auth2_oath.oathtotpsecret': {
|
|
||||||
'Meta': {'object_name': 'OATHTOTPSecret'},
|
|
||||||
'drift': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '4'}),
|
|
||||||
'key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
|
|
||||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oath_totp_secret'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['%s']" % user_model_label})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
complete_apps = ['auth2_oath']
|
|
|
@ -1,9 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
class OATHTOTPSecret(models.Model):
|
|
||||||
user = models.OneToOneField(getattr(settings, 'AUTH_USER_MODEL', 'auth.User'),
|
|
||||||
primary_key= True, related_name='oath_totp_secret')
|
|
||||||
# 20 bytes string as hexadecimal
|
|
||||||
key = models.CharField(max_length=40)
|
|
||||||
drift = models.IntegerField(default=0,max_length=4)
|
|
|
@ -1,28 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
<div id="login-oath">
|
|
||||||
|
|
||||||
<p>
|
|
||||||
{% blocktrans %}
|
|
||||||
Once you have created your account, log in with an other authentication method.
|
|
||||||
Then, in account management, follow the instructions to deploy the
|
|
||||||
One Time password authentication method.
|
|
||||||
{% endblocktrans %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<form method="post" action="">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form.as_p }}
|
|
||||||
<input type="submit" name="{{ submit_name }}" value="{% trans "Log in" %}"/>
|
|
||||||
{% if cancel %}
|
|
||||||
<input type="submit" name="cancel" value="{% trans 'Cancel' %}"/>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="login-actions">
|
|
||||||
<p>→ {% trans "Forgot password?" %} <a href="{% url 'auth_password_reset' %}">{% trans "Reset it!" %}</a></p>
|
|
||||||
<!--<p>→ {% trans "Not a member?" %} <a href="{% url 'registration_register' %}">{% trans "Register!" %}</a></p>-->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
|
@ -1,32 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
<script src="{{ STATIC_URL }}jquery/js/jquery.js"></script>
|
|
||||||
<script src="{{ STATIC_URL }}jquery/js/qrcode.js"></script>
|
|
||||||
<script src="{{ STATIC_URL }}jquery/js/jquery.qrcode.js"></script>
|
|
||||||
<h4>{% trans "Time based one-time password" %}</h4>
|
|
||||||
<div>
|
|
||||||
{% if key %}
|
|
||||||
<p>{% trans "Secret" %}: {{ key }}</p>
|
|
||||||
{% if google_authenticator %}
|
|
||||||
<p>{% trans "Google authenticator" %}: <div class="google_authenticator">{{ google_authenticator|safe }}</div></p>
|
|
||||||
{% endif %}
|
|
||||||
<p><a href="{{ bookmarklet|safe }}">{% trans "Bookmarklet" %}</a>
|
|
||||||
<p>{% trans "Copy this link to your bookmarks. When clicking on it it will generate a new one-time password which will allow you to login" %}</p></p>
|
|
||||||
<p><form action="{{ base|safe }}/delete_totp_secret{{ next|safe }}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="submit" class="submit-link" value="{% trans "Delete this credential" %}">
|
|
||||||
</form></p>
|
|
||||||
{% else %}
|
|
||||||
<p>{% trans "This kind of authentication is actually not possible, because you do not have any secret setup." %}</p>
|
|
||||||
{% endif %}
|
|
||||||
<p><form action="{{ base|safe }}/new_totp_secret{{ next|safe }}" class="submit-link" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="submit" value="{% trans "Generate a new credential" %}">
|
|
||||||
</form></p>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
$('.google_authenticator').each(function (index, element) {
|
|
||||||
var content = $(element).text();
|
|
||||||
$(element).text("");
|
|
||||||
$(element).qrcode({ "render": "table", "width": 500, "height": 500, "text": content, 'correctLevel': QRErrorCorrectLevel.M, 'typeNumber': 5 });
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.conf.urls import patterns
|
|
||||||
import views
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
|
||||||
(r'^new_totp_secret$', views.new_totp_secret),
|
|
||||||
(r'^delete_totp_secret$', views.delete_totp_secret))
|
|
|
@ -1,58 +0,0 @@
|
||||||
import urllib
|
|
||||||
import random
|
|
||||||
import base64
|
|
||||||
|
|
||||||
from django.http import HttpResponseBadRequest, HttpResponseRedirect
|
|
||||||
from django.template import RequestContext
|
|
||||||
from django.template.loader import render_to_string
|
|
||||||
|
|
||||||
import models
|
|
||||||
import authentic2.vendor.totp_js.totp_bookmarklet as totp_bookmarklet
|
|
||||||
|
|
||||||
_hexachars = '0123456789abcdef'
|
|
||||||
|
|
||||||
def new_totp_secret(request, next_url='/'):
|
|
||||||
if request.user is None or not hasattr(request.user, '_meta') \
|
|
||||||
or request.method != 'POST':
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
key = ''.join([random.choice(_hexachars) for x in range(40)])
|
|
||||||
secret, _ = models.OATHTOTPSecret.objects.get_or_create(user=request.user)
|
|
||||||
secret.key = key
|
|
||||||
secret.save()
|
|
||||||
next_url = request.REQUEST.get('next', next_url)
|
|
||||||
return HttpResponseRedirect(next_url)
|
|
||||||
|
|
||||||
def delete_totp_secret(request, next_url='/'):
|
|
||||||
if request.user is None or not hasattr(request.user, '_meta') \
|
|
||||||
or request.method != 'POST':
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
try:
|
|
||||||
models.OATHTOTPSecret.objects.filter(user=request.user).delete()
|
|
||||||
except models.OATHTOTPSecret.DoesNotExist:
|
|
||||||
pass
|
|
||||||
next_url = request.REQUEST.get('next', next_url)
|
|
||||||
return HttpResponseRedirect(next_url)
|
|
||||||
|
|
||||||
def totp_profile(request, next_url='', template_name='oath/totp_profile.html'):
|
|
||||||
if request.user is None or not hasattr(request.user, '_meta'):
|
|
||||||
return ''
|
|
||||||
if next_url:
|
|
||||||
next_url = '?next=%s' % urllib.quote(next_url)
|
|
||||||
google_authenticator, key, bookmarklet = '', '', ''
|
|
||||||
try:
|
|
||||||
secret = models.OATHTOTPSecret.objects.get(user=request.user)
|
|
||||||
key = secret.key
|
|
||||||
bookmarklet = totp_bookmarklet.otp_doc(secret.key)
|
|
||||||
google_authenticator = 'otpauth://totp/%(user)s@localhost?secret=%(b32_secret)s' % \
|
|
||||||
{ 'user': request.user.username,
|
|
||||||
'domain': request.get_host(),
|
|
||||||
'b32_secret': base64.b32encode(key.decode('hex')) }
|
|
||||||
except models.OATHTOTPSecret.DoesNotExist:
|
|
||||||
pass
|
|
||||||
return render_to_string(template_name,
|
|
||||||
{ 'key': key,
|
|
||||||
'bookmarklet': bookmarklet,
|
|
||||||
'google_authenticator': google_authenticator,
|
|
||||||
'next': next_url,
|
|
||||||
'base': '/oath'},
|
|
||||||
RequestContext(request))
|
|
|
@ -260,9 +260,6 @@ def build_assertion(request, login, nid_format='transient', attributes=None):
|
||||||
elif backend == \
|
elif backend == \
|
||||||
'authentic2.authsaml2.backends.AuthSAML2TransientBackend':
|
'authentic2.authsaml2.backends.AuthSAML2TransientBackend':
|
||||||
authn_context = lasso.SAML2_AUTHN_CONTEXT_UNSPECIFIED
|
authn_context = lasso.SAML2_AUTHN_CONTEXT_UNSPECIFIED
|
||||||
elif backend == \
|
|
||||||
'authentic2.auth2_auth.auth2_oath.backend.OATHTOTPBackend':
|
|
||||||
authn_context = lasso.SAML2_AUTHN_CONTEXT_TIME_SYNC_TOKEN
|
|
||||||
else:
|
else:
|
||||||
backend = load_backend(backend)
|
backend = load_backend(backend)
|
||||||
if hasattr(backend, 'get_saml2_authn_context'):
|
if hasattr(backend, 'get_saml2_authn_context'):
|
||||||
|
|
|
@ -317,7 +317,6 @@ ADMIN_TOOLS_MENU = 'authentic2.menu.CustomMenu'
|
||||||
AUTH_SAML2 = 'AUTH_SAML2' in os.environ
|
AUTH_SAML2 = 'AUTH_SAML2' in os.environ
|
||||||
AUTH_OPENID = 'AUTH_OPENID' in os.environ
|
AUTH_OPENID = 'AUTH_OPENID' in os.environ
|
||||||
AUTH_SSL = 'AUTH_SSL' in os.environ
|
AUTH_SSL = 'AUTH_SSL' in os.environ
|
||||||
AUTH_OATH = 'AUTH_OATH' in os.environ
|
|
||||||
IDP_SAML2 = 'IDP_SAML2' in os.environ
|
IDP_SAML2 = 'IDP_SAML2' in os.environ
|
||||||
IDP_OPENID = 'IDP_OPENID' in os.environ
|
IDP_OPENID = 'IDP_OPENID' in os.environ
|
||||||
IDP_CAS = 'IDP_CAS' in os.environ
|
IDP_CAS = 'IDP_CAS' in os.environ
|
||||||
|
@ -354,11 +353,6 @@ if AUTH_SSL:
|
||||||
AUTH_FRONTENDS += ('authentic2.auth2_auth.auth2_ssl.frontend.SSLFrontend',)
|
AUTH_FRONTENDS += ('authentic2.auth2_auth.auth2_ssl.frontend.SSLFrontend',)
|
||||||
INSTALLED_APPS += ('authentic2.auth2_auth.auth2_ssl',)
|
INSTALLED_APPS += ('authentic2.auth2_auth.auth2_ssl',)
|
||||||
|
|
||||||
if AUTH_OATH:
|
|
||||||
INSTALLED_APPS += ('authentic2.auth2_auth.auth2_oath',)
|
|
||||||
AUTHENTICATION_BACKENDS += ('authentic2.auth2_auth.auth2_oath.backend.OATHTOTPBackend',)
|
|
||||||
AUTH_FRONTENDS += ('authentic2.auth2_auth.auth2_oath.frontend.OATHOTPFrontend',)
|
|
||||||
|
|
||||||
if IDP_SAML2:
|
if IDP_SAML2:
|
||||||
IDP_BACKENDS += ('authentic2.idp.saml.backend.SamlBackend',)
|
IDP_BACKENDS += ('authentic2.idp.saml.backend.SamlBackend',)
|
||||||
|
|
||||||
|
|
|
@ -41,10 +41,6 @@ if getattr(settings, 'IDP_OPENID', False):
|
||||||
urlpatterns += patterns('',
|
urlpatterns += patterns('',
|
||||||
(r'^openid/', include('authentic2.idp.idp_openid.urls')))
|
(r'^openid/', include('authentic2.idp.idp_openid.urls')))
|
||||||
|
|
||||||
if 'authentic2.auth2_auth.auth2_oath' in settings.INSTALLED_APPS:
|
|
||||||
urlpatterns += patterns('',
|
|
||||||
(r'^oath/', include('authentic2.auth2_auth.auth2_oath.urls')))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if settings.DISCO_SERVICE:
|
if settings.DISCO_SERVICE:
|
||||||
urlpatterns += patterns('',
|
urlpatterns += patterns('',
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
- implement accept_hotp
|
|
||||||
- add truncation functions for hashing algorithm with a larger output like SHA2
|
|
||||||
variant.
|
|
|
@ -1,133 +0,0 @@
|
||||||
import hashlib
|
|
||||||
import hmac
|
|
||||||
import binascii
|
|
||||||
import time
|
|
||||||
import datetime
|
|
||||||
import calendar
|
|
||||||
|
|
||||||
'''
|
|
||||||
Python implementation of HOTP and TOTP algorithms from the OATH project.
|
|
||||||
|
|
||||||
Copyright 2010, Benjamin Dauvergne
|
|
||||||
|
|
||||||
* All rights reserved.
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.'''
|
|
||||||
|
|
||||||
def __truncated_value(h):
|
|
||||||
bytes = map(ord, h)
|
|
||||||
offset = bytes[19] & 0xf
|
|
||||||
v = (bytes[offset] & 0x7f) << 24 | (bytes[offset+1] & 0xff) << 16 | \
|
|
||||||
(bytes[offset+2] & 0xff) << 8 | (bytes[offset+3] & 0xff)
|
|
||||||
return v
|
|
||||||
|
|
||||||
def dec(h,p):
|
|
||||||
v = str(__truncated_value(h))
|
|
||||||
return v[len(v)-p:]
|
|
||||||
|
|
||||||
def __hotp(key, counter, hash=hashlib.sha1):
|
|
||||||
hex_counter = hex(long(counter))[2:-1]
|
|
||||||
hex_counter = '0' * (16 - len(hex_counter)) + hex_counter
|
|
||||||
bin_counter = binascii.unhexlify(hex_counter)
|
|
||||||
bin_key = binascii.unhexlify(key)
|
|
||||||
|
|
||||||
return hmac.new(bin_key, bin_counter, hash).digest()
|
|
||||||
|
|
||||||
def hotp(key,counter,format='dec6',hash=hashlib.sha1):
|
|
||||||
'''Compute a HOTP value as prescribed by RFC4226
|
|
||||||
|
|
||||||
See http://tools.ietf.org/html/rfc4226
|
|
||||||
'''
|
|
||||||
bin_hotp = __hotp(key, counter, hash)
|
|
||||||
|
|
||||||
if format == 'hex40':
|
|
||||||
return binascii.hexlify(bin_hotp[0:5])
|
|
||||||
elif format == 'dec6':
|
|
||||||
return dec(bin_hotp, 6)
|
|
||||||
elif format == 'dec7':
|
|
||||||
return dec(bin_hotp, 7)
|
|
||||||
elif format == 'dec8':
|
|
||||||
return dec(bin_hotp, 8)
|
|
||||||
else:
|
|
||||||
raise ValueError('unknown format')
|
|
||||||
|
|
||||||
def totp(key, format='dec8', period=30, t=None, hash=hashlib.sha1):
|
|
||||||
'''Compute a TOTP value as prescribed by OATH specifications.
|
|
||||||
|
|
||||||
See http://tools.ietf.org/html/draft-mraihi-totp-timebased-06
|
|
||||||
'''
|
|
||||||
if t is None:
|
|
||||||
t = time.time()
|
|
||||||
else:
|
|
||||||
if isinstance(t, datetime.datetime):
|
|
||||||
t = calendar.timegm(t.utctimetuple())
|
|
||||||
else:
|
|
||||||
t = int(t)
|
|
||||||
T = int(t/period)
|
|
||||||
return hotp(key, T, format=format, hash=hash)
|
|
||||||
|
|
||||||
def accept_totp(key, response, period=30, format='dec8', hash=hashlib.sha1,
|
|
||||||
forward_drift=1, backward_drift=1, drift=0, t=None):
|
|
||||||
'''Validate a TOTP value inside a window of
|
|
||||||
[drift-bacward_drift:drift+forward_drift] of time steps.
|
|
||||||
Where drift is the drift obtained during the last call to accept_totp.
|
|
||||||
|
|
||||||
Return a pair (v,d) where v is a boolean giving the result, and d the
|
|
||||||
needed drift to validate the value. The drift value should be saved for
|
|
||||||
user with later call to accept_totp in order to accept a slowly
|
|
||||||
accumulating drift with a token clock.
|
|
||||||
'''
|
|
||||||
t = t or time.time()
|
|
||||||
for i in range(-backward_drift,forward_drift+1):
|
|
||||||
d = (drift+i) * period
|
|
||||||
if totp(key, format=format, period=period, hash=hash, t=t+d) == response:
|
|
||||||
return True, drift+i
|
|
||||||
return False, 0
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Test vectors extracted from RFC 4226
|
|
||||||
secret = '3132333435363738393031323334353637383930'
|
|
||||||
tvector = [
|
|
||||||
(0, 'cc93cf18508d94934c64b65d8ba7667fb7cde4b0'),
|
|
||||||
(1, '75a48a19d4cbe100644e8ac1397eea747a2d33ab'),
|
|
||||||
(2, '0bacb7fa082fef30782211938bc1c5e70416ff44'),
|
|
||||||
(3, '66c28227d03a2d5529262ff016a1e6ef76557ece'),
|
|
||||||
(4, 'a904c900a64b35909874b33e61c5938a8e15ed1c'),
|
|
||||||
(5, 'a37e783d7b7233c083d4f62926c7a25f238d0316'),
|
|
||||||
(6, 'bc9cd28561042c83f219324d3c607256c03272ae'),
|
|
||||||
(7, 'a4fb960c0bc06e1eabb804e5b397cdc4b45596fa'),
|
|
||||||
(8, '1b3c89f65e6c9e883012052823443f048b4332db'),
|
|
||||||
(9, '1637409809a679dc698207310c8c7fc07290d9e5'), ]
|
|
||||||
for counter, value in tvector:
|
|
||||||
assert(binascii.hexlify(__hotp(secret, counter)) == value)
|
|
||||||
tvector2 = [
|
|
||||||
(0, '4c93cf18', '1284755224', '755224',),
|
|
||||||
(1, '41397eea', '1094287082', '287082',),
|
|
||||||
(2, '82fef30', '137359152', '359152',),
|
|
||||||
(3, '66ef7655', '1726969429', '969429',),
|
|
||||||
(4, '61c5938a', '1640338314', '338314',),
|
|
||||||
(5, '33c083d4', '868254676', '254676',),
|
|
||||||
(6, '7256c032', '1918287922', '287922',),
|
|
||||||
(7, '4e5b397', '82162583', '162583',),
|
|
||||||
(8, '2823443f', '673399871', '399871',),
|
|
||||||
(9, '2679dc69', '645520489', '520489',),]
|
|
||||||
for counter, hexa, deci, trunc in tvector2:
|
|
||||||
h = __hotp(secret, counter)
|
|
||||||
v = __truncated_value(h)
|
|
||||||
assert(hex(v)[2:] == hexa)
|
|
||||||
assert(str(v) == deci)
|
|
||||||
assert(dec(h,6) == trunc)
|
|
||||||
secret = binascii.hexlify('12345678901234567890')
|
|
||||||
tvector3 = [
|
|
||||||
(59, hashlib.sha1, '94287082'),
|
|
||||||
(1111111109, hashlib.sha1, '07081804') ]
|
|
||||||
for timestamp, hash, value in tvector3:
|
|
||||||
assert (totp(secret,t=datetime.datetime.utcfromtimestamp(timestamp),hash=hash) == value)
|
|
||||||
assert(accept_totp(secret, '94287082', t=65) == (True, -1))
|
|
||||||
assert(accept_totp(secret, '94287082', t=65, drift=-1) == (True, -1))
|
|
|
@ -1,8 +0,0 @@
|
||||||
Simple data document generator containing a TOTP soft token
|
|
||||||
===========================================================
|
|
||||||
|
|
||||||
To use it from your python application just do:
|
|
||||||
|
|
||||||
import totp_bookmarklet
|
|
||||||
|
|
||||||
html_fragment = '<a href="%s">OTP Bookmarklet</a>' % totp_bookmarklet.otp_doc('my_secret')
|
|
File diff suppressed because one or more lines are too long
|
@ -1,110 +0,0 @@
|
||||||
/*
|
|
||||||
A simple Javascript HOTP implementation (HMAC-Based One-Time Password Algorithm) as described in RFC 4226.
|
|
||||||
|
|
||||||
The library is relying on crypto-js (http://code.google.com/p/crypto-js/) for the javascript HMAC-SHA1 implementation.
|
|
||||||
|
|
||||||
The library can be used to create software token (don't forget to protect the key of the token...).
|
|
||||||
|
|
||||||
If you want to use the library, you'll need to load the crypto-js (sha1 and hmac) and hotp.js.
|
|
||||||
|
|
||||||
Calling the library is easy, you just have to set the hex key of the token, the counter plus the output format.
|
|
||||||
|
|
||||||
otp = hotp("3132333435363738393031323334353637383930","4","dec6");
|
|
||||||
|
|
||||||
Current output formats are : hex40 (format used by ootp, a free software library) and dec6 (the 6 decimal digit as described in the RFC 4226).
|
|
||||||
|
|
||||||
A demo page with the test vector of the RFC 4226 : http://www.foo.be/hotp/example.html*
|
|
||||||
|
|
||||||
http://www.gitorious.org/hotp-js/
|
|
||||||
|
|
||||||
Copyright (C) 2009 Alexandre Dulaunoy
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as
|
|
||||||
published by the Free Software Foundation, either version 3 of the
|
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
function hotp(key, counter, format) {
|
|
||||||
|
|
||||||
function hotp_hexkeytobytestream(s) {
|
|
||||||
// s is the key to be converted in bytes
|
|
||||||
var b = new Array();
|
|
||||||
var last = s.length;
|
|
||||||
for (var i = 0; i < last; i = i + 2) {
|
|
||||||
var x = s[i] + s[i + 1];
|
|
||||||
x.toUpperCase();
|
|
||||||
x = "0x" + x;
|
|
||||||
x = parseInt(x);
|
|
||||||
b[i] = String.fromCharCode(x);
|
|
||||||
}
|
|
||||||
var ret = new String();
|
|
||||||
ret = b.join('');
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
}
|
|
||||||
function hotp_movingfactortohex(count) {
|
|
||||||
// count is the moving factor in OTP to be converted in bytes
|
|
||||||
v = decimaltohex(count, 16);
|
|
||||||
var decb = new Array();
|
|
||||||
lhex = Crypto.util.hexToBytes(v);
|
|
||||||
for (var i = 0; i < lhex.length; i++) {
|
|
||||||
decb[i] = String.fromCharCode(lhex[i]);
|
|
||||||
}
|
|
||||||
var retval = new String();
|
|
||||||
retval = decb.join('');
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
function decimaltohex(d, padding) {
|
|
||||||
// d is the decimal value
|
|
||||||
// padding is the padding to apply (O pad)
|
|
||||||
var hex = Number(d).toString(16);
|
|
||||||
padding = typeof(padding) === "undefined" || padding === null ? padding = 2 : padding;
|
|
||||||
while (hex.length < padding) {
|
|
||||||
hex = "0" + hex;
|
|
||||||
}
|
|
||||||
return hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
function truncatedvalue(h, p) {
|
|
||||||
// h is the hash value
|
|
||||||
// p is precision
|
|
||||||
offset = h[19] & 0xf;
|
|
||||||
v = (h[offset] & 0x7f) << 24 | (h[offset + 1] & 0xff) << 16 | (h[offset + 2] & 0xff) << 8 | (h[offset + 3] & 0xff);
|
|
||||||
v = "" + v;
|
|
||||||
v = v.substr(v.length - p, p);
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hmacBytes = Crypto.HMAC(Crypto.SHA1, Crypto.charenc.Binary.stringToBytes((hotp_movingfactortohex(counter))), Crypto.charenc.Binary.stringToBytes(hotp_hexkeytobytestream(key)));
|
|
||||||
|
|
||||||
if (format == "hex40") {
|
|
||||||
return hmacBytes.substring(0, 10);
|
|
||||||
} else if (format == "dec6") {
|
|
||||||
return truncatedvalue(Crypto.util.hexToBytes(hmacBytes), 6);
|
|
||||||
} else if (format == "dec7") {
|
|
||||||
return truncatedvalue(Crypto.util.hexToBytes(hmacBytes), 7);
|
|
||||||
} else if (format == "dec8") {
|
|
||||||
return truncatedvalue(Crypto.util.hexToBytes(hmacBytes), 8);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "unknown format";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function totp(key,format) {
|
|
||||||
var T = parseInt(new Date().getTime()/30000);
|
|
||||||
return hotp(key,T,format);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
function otp() {
|
|
||||||
var key = 'FAFA';
|
|
||||||
alert('Code: ' + totp(key, 'MODE'));
|
|
||||||
};
|
|
||||||
otp();
|
|
|
@ -1,30 +0,0 @@
|
||||||
import binascii
|
|
||||||
import base64
|
|
||||||
import os.path
|
|
||||||
|
|
||||||
def __content(f):
|
|
||||||
return open(os.path.join(os.path.dirname(__file__), f)).read()
|
|
||||||
|
|
||||||
crypto_js = __content('js/crypto.js')
|
|
||||||
hotp_js = __content('js/hotp.js')
|
|
||||||
myotp_js = __content('js/my-otp.js')
|
|
||||||
|
|
||||||
|
|
||||||
def dataize(document, type='text/html'):
|
|
||||||
return 'data:%s;base64,%s' % (type, base64.b64encode(document))
|
|
||||||
|
|
||||||
def otp_doc(key,mode='dec6'):
|
|
||||||
'''Convert an hexadecimal key to a document able to produce TOTP keys using
|
|
||||||
the dec6 mode
|
|
||||||
'''
|
|
||||||
doc = ''''<html>
|
|
||||||
<body>
|
|
||||||
<script type="text/javascript">%s;history.back()</script>
|
|
||||||
</body>
|
|
||||||
</html>''' % (crypto_js + ';' + hotp_js + ';' + \
|
|
||||||
myotp_js.replace('FAFA',key).replace('MODE',mode))
|
|
||||||
return dataize(doc)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import sys
|
|
||||||
print '''<html><body><a href="%s" title="Drag me to your bookmark">OTP Password</a></body></html>''' % otp_doc(sys.argv[1])
|
|
Loading…
Reference in New Issue