0003-auth_oath-allow-user-to-enable-OATH-factor.patch
src/authentic2/auth2_multifactor/auth_oath/authenticators.py | ||
---|---|---|
10 | 10 |
class TOTPAuthenticator(object): |
11 | 11 |
submit_name = 'oath-totp-submit' |
12 | 12 |
auth_level = app_settings.LEVEL |
13 |
_id = 'multifactor-totp' |
|
13 | 14 | |
14 | 15 |
def enabled(self): |
15 | 16 |
return app_settings.ENABLE |
... | ... | |
18 | 19 |
return ugettext_lazy('One-time password') |
19 | 20 | |
20 | 21 |
def id(self): |
21 |
return 'multifactor-totp'
|
|
22 |
return self._id
|
|
22 | 23 | |
23 | 24 |
def login(self, request, *args, **kwargs): |
24 | 25 |
return totp_login(request, *args, **kwargs) |
src/authentic2/auth2_multifactor/auth_oath/templates/auth_oath/totp_enable.html | ||
---|---|---|
1 |
{% extends "authentic2/base-page.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block content %} |
|
5 |
<div> |
|
6 |
<p> |
|
7 |
{% blocktrans %} |
|
8 |
Enable this factor in order to secure authentication with a one-time |
|
9 |
verification code. It will be requested at login on top of username and |
|
10 |
password. |
|
11 |
{% endblocktrans %}<br /> |
|
12 |
{% blocktrans %} |
|
13 |
Install any application capable of generating such codes (refer to the |
|
14 |
documentation for recommendations). Then scan the QR code below, and enter a |
|
15 |
valid code in order to complete the setup. |
|
16 |
{% endblocktrans %} |
|
17 |
</p> |
|
18 |
</div> |
|
19 |
<div> |
|
20 |
<p> |
|
21 |
<img src="data:image/png;base64,{{ uri }}"> |
|
22 |
</p> |
|
23 |
<p> |
|
24 |
{% trans "For manual setup when unable to scan the code, use the following key : " %} |
|
25 |
{{ secret }} |
|
26 |
</p> |
|
27 |
</div> |
|
28 | ||
29 |
<div> |
|
30 |
<form method="post" autocomplete="off" action=""> |
|
31 |
{% csrf_token %} |
|
32 |
{{ form.as_p }} |
|
33 |
<button class="submit-button" name="{{ submit_name }}">{% trans "Activate" %}</button> |
|
34 |
</form> |
|
35 |
</div> |
|
36 |
{% endblock %} |
src/authentic2/auth2_multifactor/auth_oath/templates/auth_oath/totp_profile.html | ||
---|---|---|
1 | 1 |
{% load i18n %} |
2 | 2 | |
3 |
<h4>QR Code</h4> |
|
3 |
{% if enabled %} |
|
4 |
<h4>QR Code</h4> |
|
5 |
<div> |
|
6 |
<p> |
|
7 |
<img src="data:image/png;base64,{{ uri }}"> |
|
8 |
</p> |
|
9 |
</div> |
|
4 | 10 | |
5 |
<div> |
|
6 |
<p> |
|
7 |
<img src="data:image/png;base64,{{ uri }}"> |
|
8 |
</p> |
|
9 |
</div> |
|
10 | ||
11 |
<a onclick="return confirm('{% trans "Warning: you will have to configure your devices again." %}')" href="{% url 'totp-change-secret' %}">{% trans "Generate new code" %}</a><br /> |
|
11 |
<a onclick="return confirm('{% trans "Warning: you will have to configure your devices again." %}')" href="{% url 'totp-change-secret' %}">{% trans "Generate new code" %}</a><br /> |
|
12 |
<a href="{% url 'totp-disable' %}">{% trans "Disable factor" %}</a> |
|
13 |
{% else %} |
|
14 |
{% if login_url %} |
|
15 |
<a href="{{ login_url }}"> |
|
16 |
{% trans "Insufficient authentication level to view, click here to increase." %} |
|
17 |
</a> |
|
18 |
{% else %} |
|
19 |
<a href="{{ enable_url }}"> |
|
20 |
{% trans "This authentication factor is disabled, click here to set it up." %} |
|
21 |
</a> |
|
22 |
{% endif %} |
|
23 |
{% endif %} |
src/authentic2/auth2_multifactor/auth_oath/urls.py | ||
---|---|---|
5 | 5 | |
6 | 6 |
urlpatterns = [ |
7 | 7 |
url(r'change_secret', views.change_secret, name='totp-change-secret'), |
8 |
url(r'enable', views.enable, name='totp-enable'), |
|
9 |
url(r'disable', views.disable, name='totp-disable'), |
|
8 | 10 |
] |
src/authentic2/auth2_multifactor/auth_oath/utils.py | ||
---|---|---|
1 |
from base64 import b64encode |
|
1 |
from base64 import b64encode, b32encode |
|
2 |
from binascii import unhexlify |
|
2 | 3 |
from io import BytesIO |
3 | 4 |
from random import SystemRandom |
4 | 5 | |
... | ... | |
33 | 34 |
return secret |
34 | 35 | |
35 | 36 | |
37 |
def get_encoded_secret(user): |
|
38 |
key = user.oath_totp_secret.key |
|
39 |
return b32encode(unhexlify(key)).decode('ascii') |
|
40 | ||
41 | ||
42 |
def get_authenticator_id(): |
|
43 |
from .authenticators import TOTPAuthenticator |
|
44 |
return TOTPAuthenticator._id |
|
45 | ||
46 | ||
36 | 47 |
def get_authenticator_level(): |
37 | 48 |
from .authenticators import TOTPAuthenticator |
38 | 49 |
return TOTPAuthenticator.auth_level |
src/authentic2/auth2_multifactor/auth_oath/views.py | ||
---|---|---|
5 | 5 | |
6 | 6 |
from oath import accept_totp |
7 | 7 | |
8 |
from authentic2.utils import redirect, get_next_url, csrf_token_check |
|
8 |
from authentic2.utils import redirect, get_next_url, csrf_token_check, make_url
|
|
9 | 9 | |
10 | 10 |
from .forms import TOTPForm |
11 |
from .utils import get_qrcode, set_secret, get_authenticator_level |
|
11 |
from .utils import (get_qrcode, set_secret, get_authenticator_level, |
|
12 |
get_authenticator_id, get_encoded_secret) |
|
12 | 13 | |
13 | 14 | |
14 | 15 |
def totp_profile(request, *args, **kwargs): |
15 | 16 |
context = {} |
16 | 17 |
context['uri'] = get_qrcode(request.user) |
18 |
try: |
|
19 |
request.user.enabled_auth_factors.get( |
|
20 |
authenticator_id=get_authenticator_id()) |
|
21 |
except request.user.enabled_auth_factors.model.DoesNotExist: |
|
22 |
context['enabled'] = False |
|
23 |
if 'auth_level' in request.GET: |
|
24 |
# Forced enabling flow |
|
25 |
context['enable_url'] = make_url('totp-enable', keep_params=True, |
|
26 |
request=request) |
|
27 |
else: |
|
28 |
context['enable_url'] = make_url('totp-enable', |
|
29 |
params={'next': request.get_full_path()}) |
|
30 |
else: |
|
31 |
auth_level = get_authenticator_level() |
|
32 |
if request.session.get('auth_level', 1) < auth_level: |
|
33 |
params = { |
|
34 |
'next': request.get_full_path(), |
|
35 |
'auth_level': auth_level, |
|
36 |
} |
|
37 |
context['login_url'] = make_url('auth_login', params=params) |
|
38 |
else: |
|
39 |
context['enabled'] = True |
|
17 | 40 |
return render_to_string('auth_oath/totp_profile.html', context, request=request) |
18 | 41 | |
19 | 42 |
class Login(FormView): |
... | ... | |
35 | 58 |
if success: |
36 | 59 |
secret.drift = drift |
37 | 60 |
secret.save() |
38 |
self.request.session['auth_level'] = get_authenticator_level()
|
|
61 |
self.set_auth_level()
|
|
39 | 62 |
return super(Login, self).form_valid(form) |
40 | 63 |
form.add_error('code', _('Invalid code.')) |
41 | 64 |
return self.form_invalid(form) |
42 | 65 | |
66 |
def set_auth_level(self): |
|
67 |
self.request.session['auth_level'] = get_authenticator_level() |
|
68 | ||
43 | 69 |
totp_login = Login.as_view() |
44 | 70 | |
45 | 71 | |
... | ... | |
47 | 73 |
def change_secret(request): |
48 | 74 |
set_secret(request.user) |
49 | 75 |
return redirect(request, 'account_management') |
76 | ||
77 | ||
78 |
class Enable(Login): |
|
79 |
"""Allow the user to setup TOTP authentication. |
|
80 | ||
81 |
Require a valid OTP in order to complete the process, ensuring that the |
|
82 |
user setup is successful. |
|
83 |
""" |
|
84 |
template_name = 'auth_oath/totp_enable.html' |
|
85 | ||
86 |
def dispatch(self, request, *args, **kwargs): |
|
87 |
try: |
|
88 |
# Verify that the factor is not already enabled |
|
89 |
request.user.enabled_auth_factors.get( |
|
90 |
authenticator_id=get_authenticator_id()) |
|
91 |
return redirect(request, 'account_management') |
|
92 |
except request.user.enabled_auth_factors.model.DoesNotExist: |
|
93 |
return super(Enable, self).dispatch(request, *args, **kwargs) |
|
94 | ||
95 |
def get_context_data(self, **kwargs): |
|
96 |
kwargs['uri'] = get_qrcode(self.request.user) |
|
97 |
# Also allow for manual input |
|
98 |
secret = get_encoded_secret(self.request.user) |
|
99 |
kwargs['secret'] = ' '.join(secret[i:i + 4] |
|
100 |
for i in range(0, len(secret), 4)) |
|
101 |
return super(Enable, self).get_context_data(**kwargs) |
|
102 | ||
103 |
def set_auth_level(self): |
|
104 |
self.request.user.enabled_auth_factors.create( |
|
105 |
authenticator_id=get_authenticator_id()) |
|
106 |
return super(Enable, self).set_auth_level() |
|
107 | ||
108 |
enable = login_required(Enable.as_view()) |
|
109 | ||
110 | ||
111 |
@login_required |
|
112 |
def disable(request): |
|
113 |
try: |
|
114 |
factor = request.user.enabled_auth_factors.get( |
|
115 |
authenticator_id=get_authenticator_id()) |
|
116 |
factor.delete() |
|
117 |
except request.user.enabled_auth_factors.model.DoesNotExist: |
|
118 |
pass |
|
119 |
return redirect(request, 'account_management') |
|
50 |
- |