codeforlife-portal 7.4.8__py2.py3-none-any.whl → 8.0.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of codeforlife-portal might be problematic. Click here for more details.
- cfl_common/setup.py +10 -5
- {codeforlife_portal-7.4.8.dist-info → codeforlife_portal-8.0.0.dist-info}/METADATA +7 -14
- {codeforlife_portal-7.4.8.dist-info → codeforlife_portal-8.0.0.dist-info}/RECORD +30 -30
- deploy/middleware/security.py +4 -5
- example_project/portal_test_settings.py +1 -1
- example_project/settings.py +2 -2
- example_project/urls.py +4 -5
- portal/__init__.py +1 -1
- portal/admin.py +26 -24
- portal/forms/play.py +2 -2
- portal/forms/registration.py +2 -2
- portal/forms/teach.py +2 -2
- portal/helpers/decorators.py +18 -18
- portal/helpers/password.py +52 -54
- portal/templates/{captcha → django_recaptcha}/includes/js_v2_invisible.html +3 -3
- portal/templates/{captcha → django_recaptcha}/widget_v2_invisible.html +2 -2
- portal/templates/two_factor/core/login.html +66 -59
- portal/templates/two_factor/core/setup.html +58 -49
- portal/templates/two_factor/profile/profile.html +35 -17
- portal/tests/test_captcha_forms.py +2 -2
- portal/tests/test_middleware.py +32 -10
- portal/urls.py +94 -88
- portal/views/api.py +30 -16
- portal/views/home.py +45 -15
- portal/views/registration.py +3 -3
- portal/views/two_factor/core.py +22 -19
- portal/views/two_factor/profile.py +2 -2
- {codeforlife_portal-7.4.8.dist-info → codeforlife_portal-8.0.0.dist-info}/LICENSE.md +0 -0
- {codeforlife_portal-7.4.8.dist-info → codeforlife_portal-8.0.0.dist-info}/WHEEL +0 -0
- {codeforlife_portal-7.4.8.dist-info → codeforlife_portal-8.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,63 +1,70 @@
|
|
|
1
1
|
{# Overriden the original "two_factor" template to remove extending base template and slightly adjust style #}
|
|
2
2
|
|
|
3
|
-
{% load i18n
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
3
|
+
{% load i18n %}
|
|
4
|
+
{% load two_factor_tags %}
|
|
5
|
+
|
|
6
|
+
{% block extra_media %}
|
|
7
|
+
{{ form.media }}
|
|
8
|
+
{% endblock %}
|
|
9
|
+
|
|
10
|
+
{% block content %}
|
|
11
|
+
|
|
12
|
+
<section class="row mx-0">
|
|
13
|
+
<span class="oval-blue login-shape-left"></span>
|
|
14
|
+
<span class="polygon-yellow login-shape-right"></span>
|
|
15
|
+
<div class="form--login is-flex form--register--teacher">
|
|
16
|
+
<div class="form col-xs-12 col-sm-8 col-md-8 col-lg-9 center-block">
|
|
17
|
+
<h4>{% block title %}{% trans "Welcome" %}{% endblock %}</h4>
|
|
18
|
+
|
|
19
|
+
{% if wizard.steps.current == 'auth' %}
|
|
20
|
+
<p>{% blocktrans %}Enter your credentials.{% endblocktrans %}</p>
|
|
21
|
+
{% elif wizard.steps.current == 'token' %}
|
|
22
|
+
<p>{{ device|as_verbose_action }}</p>
|
|
23
|
+
{% elif wizard.steps.current == 'backup' %}
|
|
24
|
+
<p>{% blocktrans trimmed %}Use this form for entering backup tokens for logging in.
|
|
25
|
+
These tokens have been generated for you to print and keep safe. Please
|
|
26
|
+
enter one of these backup tokens to log into your account.{% endblocktrans %}</p>
|
|
27
|
+
{% endif %}
|
|
28
|
+
|
|
29
|
+
<form action="" method="post">
|
|
30
|
+
{% block main_form_content %}
|
|
31
|
+
{% csrf_token %}
|
|
32
|
+
{% include "two_factor/_wizard_forms.html" %}
|
|
33
|
+
|
|
34
|
+
{# hidden submit button to enable [enter] key #}
|
|
35
|
+
<input type="submit" value="" hidden />
|
|
36
|
+
|
|
37
|
+
{% if other_devices %}
|
|
38
|
+
<p>{% trans "Or, alternatively, use one of your other authentication methods:" %}</p>
|
|
39
|
+
<p>
|
|
40
|
+
{% for other in other_devices %}
|
|
41
|
+
<button name="challenge_device" value="{{ other.persistent_id }}"
|
|
42
|
+
class="btn btn-secondary btn-block" type="submit">
|
|
43
|
+
{{ other|as_action }}
|
|
44
|
+
</button>
|
|
45
|
+
{% endfor %}</p>
|
|
29
46
|
{% endif %}
|
|
30
47
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{%
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<button name="wizard_goto_step" type="submit" value="backup"
|
|
55
|
-
class="button button--primary">{% trans "Use a backup token" %}</button>
|
|
56
|
-
</div>
|
|
57
|
-
</p>
|
|
58
|
-
{% endif %}
|
|
59
|
-
{% include "two_factor/_wizard_actions_submit.html" %}
|
|
60
|
-
</form>
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
</section>
|
|
48
|
+
{% include "two_factor/_wizard_actions.html" %}
|
|
49
|
+
{% endblock %}
|
|
50
|
+
</form>
|
|
51
|
+
|
|
52
|
+
{% block 'backup_tokens' %}
|
|
53
|
+
{% if backup_tokens %}
|
|
54
|
+
<hr>
|
|
55
|
+
<div class="backup_tokens_form">
|
|
56
|
+
<form action="" method="post">
|
|
57
|
+
{% csrf_token %}
|
|
58
|
+
<p>{% trans "As a last resort, you can use a backup token:" %}</p>
|
|
59
|
+
<p>
|
|
60
|
+
<button name="wizard_goto_step" type="submit" value="backup"
|
|
61
|
+
class="button button--primary">{% trans "Use Backup Token" %}</button>
|
|
62
|
+
</p>
|
|
63
|
+
</form>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</section>
|
|
68
|
+
{% endif %}
|
|
69
|
+
{% endblock %}
|
|
70
|
+
{% endblock %}
|
|
@@ -3,55 +3,64 @@
|
|
|
3
3
|
{% extends "two_factor/_base_focus.html" %}
|
|
4
4
|
{% load i18n %}
|
|
5
5
|
|
|
6
|
+
{% block extra_media %}
|
|
7
|
+
{{ form.media }}
|
|
8
|
+
{% endblock %}
|
|
9
|
+
|
|
6
10
|
{% block content %}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
{%
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{%
|
|
11
|
+
<h4>{% block title %}{% trans "Enable Two-Factor Authentication" %}{% endblock %}</h4>
|
|
12
|
+
{% if wizard.steps.current == 'welcome' %}
|
|
13
|
+
<p>{% blocktrans trimmed %}You are about to take your account security to the
|
|
14
|
+
next level. Follow the steps in this wizard to enable two-factor
|
|
15
|
+
authentication.{% endblocktrans %}</p>
|
|
16
|
+
{% elif wizard.steps.current == 'method' %}
|
|
17
|
+
<p>{% blocktrans trimmed %}Please select which authentication method you would
|
|
18
|
+
like to use.{% endblocktrans %}</p>
|
|
19
|
+
{% elif wizard.steps.current == 'generator' %}
|
|
20
|
+
<p>{% blocktrans trimmed %}To start using a token generator, please use your
|
|
21
|
+
smartphone to scan the QR code below. For example, use Google
|
|
22
|
+
Authenticator.{% endblocktrans %}</p>
|
|
23
|
+
<p><img src="{{ QR_URL }}" alt="QR Code" class="bg-white"/></p>
|
|
24
|
+
<p>{% blocktrans trimmed %}Alternatively you can use the following secret to
|
|
25
|
+
set up TOTP in your authenticator or password manager manually.{% endblocktrans %}</p>
|
|
26
|
+
<p>{% translate "TOTP Secret:" %} <a href="{{ otpauth_url }}">{{ secret_key }}</a></p>
|
|
27
|
+
<p>{% blocktrans %}Then, enter the token generated by the app.{% endblocktrans %}</p>
|
|
28
|
+
|
|
29
|
+
{% elif wizard.steps.current == 'sms' %}
|
|
30
|
+
<p>{% blocktrans trimmed %}Please enter the phone number you wish to receive the
|
|
31
|
+
text messages on. This number will be validated in the next step.
|
|
32
|
+
{% endblocktrans %}</p>
|
|
33
|
+
{% elif wizard.steps.current == 'call' %}
|
|
34
|
+
<p>{% blocktrans trimmed %}Please enter the phone number you wish to be called on.
|
|
35
|
+
This number will be validated in the next step. {% endblocktrans %}</p>
|
|
36
|
+
{% elif wizard.steps.current == 'validation' %}
|
|
37
|
+
{% if challenge_succeeded %}
|
|
38
|
+
{% if device.method == 'call' %}
|
|
39
|
+
<p>{% blocktrans trimmed %}We are calling your phone right now, please enter the
|
|
40
|
+
digits you hear.{% endblocktrans %}</p>
|
|
41
|
+
{% elif device.method == 'sms' %}
|
|
42
|
+
<p>{% blocktrans trimmed %}We sent you a text message, please enter the tokens we
|
|
43
|
+
sent.{% endblocktrans %}</p>
|
|
44
|
+
{% endif %}
|
|
45
|
+
{% else %}
|
|
46
|
+
<p class="alert alert-warning" role="alert">{% blocktrans trimmed %}We've
|
|
47
|
+
encountered an issue with the selected authentication method. Please
|
|
48
|
+
go back and verify that you entered your information correctly, try
|
|
49
|
+
again, or use a different authentication method instead. If the issue
|
|
50
|
+
persists, contact the site administrator.{% endblocktrans %}</p>
|
|
51
|
+
{% endif %}
|
|
52
|
+
{% elif wizard.steps.current == 'yubikey' %}
|
|
53
|
+
<p>{% blocktrans trimmed %}To identify and verify your YubiKey, please insert a
|
|
54
|
+
token in the field below. Your YubiKey will be linked to your
|
|
55
|
+
account.{% endblocktrans %}</p>
|
|
56
|
+
{% endif %}
|
|
57
|
+
|
|
58
|
+
<form action="" method="post">{% csrf_token %}
|
|
59
|
+
{% include "two_factor/_wizard_forms.html" %}
|
|
60
|
+
|
|
61
|
+
{# hidden submit button to enable [enter] key #}
|
|
62
|
+
<input type="submit" value="" hidden />
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{% include "two_factor/_wizard_actions_enable_2fa.html" %}
|
|
56
|
-
</form>
|
|
64
|
+
{% include "two_factor/_wizard_actions.html" %}
|
|
65
|
+
</form>
|
|
57
66
|
{% endblock %}
|
|
@@ -1,41 +1,59 @@
|
|
|
1
1
|
{% extends "two_factor/_base.html" %}
|
|
2
|
-
{% load
|
|
2
|
+
{% load i18n %}
|
|
3
|
+
{% load two_factor_tags %}
|
|
3
4
|
|
|
4
5
|
{% block content %}
|
|
5
|
-
<
|
|
6
|
+
<h1>{% block title %}{% trans "Account Security" %}{% endblock %}</h1>
|
|
6
7
|
|
|
7
8
|
{% if default_device %}
|
|
8
|
-
{%
|
|
9
|
-
|
|
10
|
-
{%
|
|
11
|
-
<
|
|
12
|
-
{%
|
|
13
|
-
|
|
9
|
+
<p>{% blocktrans with primary=default_device|as_action %}Primary method: {{ primary }}{% endblocktrans %}</p>
|
|
10
|
+
|
|
11
|
+
{% if available_phone_methods %}
|
|
12
|
+
<h2>{% trans "Backup Phone Numbers" %}</h2>
|
|
13
|
+
<p>{% blocktrans trimmed %}If your primary method is not available, we are able to
|
|
14
|
+
send backup tokens to the phone numbers listed below.{% endblocktrans %}</p>
|
|
15
|
+
{% if backup_phones %}
|
|
16
|
+
<ul>
|
|
17
|
+
{% for phone in backup_phones %}
|
|
18
|
+
<li>
|
|
19
|
+
{{ phone|as_action }}
|
|
20
|
+
<form method="post" action="{% url 'two_factor:phone_delete' phone.id %}"
|
|
21
|
+
onsubmit="return confirm({% trans 'Are you sure?' %})">
|
|
22
|
+
{% csrf_token %}
|
|
23
|
+
<button class="btn btn-sm btn-warning"
|
|
24
|
+
type="submit">{% trans "Unregister" %}</button>
|
|
25
|
+
</form>
|
|
26
|
+
</li>
|
|
27
|
+
{% endfor %}
|
|
28
|
+
</ul>
|
|
29
|
+
{% endif %}
|
|
30
|
+
<p><a href="{% url 'two_factor:phone_create' %}"
|
|
31
|
+
class="btn btn-info">{% trans "Add Phone Number" %}</a></p>
|
|
14
32
|
{% endif %}
|
|
15
33
|
|
|
16
|
-
<
|
|
34
|
+
<h2>{% trans "Backup Tokens" %}</h2>
|
|
17
35
|
<p>
|
|
18
|
-
{% blocktrans %}If you don't have any device with you, you can access
|
|
36
|
+
{% blocktrans trimmed %}If you don't have any device with you, you can access
|
|
19
37
|
your account using backup tokens.{% endblocktrans %}
|
|
20
|
-
{% blocktrans count counter=backup_tokens %}
|
|
38
|
+
{% blocktrans trimmed count counter=backup_tokens %}
|
|
21
39
|
You have only one backup token remaining.
|
|
22
40
|
{% plural %}
|
|
23
41
|
You have {{ counter }} backup tokens remaining.
|
|
24
42
|
{% endblocktrans %}
|
|
25
43
|
</p>
|
|
26
44
|
<p><a href="{% url 'two_factor:backup_tokens' %}"
|
|
27
|
-
class="
|
|
45
|
+
class="btn btn-info">{% trans "Show Codes" %}</a></p>
|
|
28
46
|
|
|
29
|
-
<
|
|
30
|
-
<p>{% blocktrans %}However we strongly discourage you to do so, you can
|
|
47
|
+
<h3>{% trans "Disable Two-Factor Authentication" %}</h3>
|
|
48
|
+
<p>{% blocktrans trimmed %}However we strongly discourage you to do so, you can
|
|
31
49
|
also disable two-factor authentication for your account.{% endblocktrans %}</p>
|
|
32
|
-
<p><a class="
|
|
50
|
+
<p><a class="btn btn-secondary" href="{% url 'two_factor:disable' %}">
|
|
33
51
|
{% trans "Disable Two-Factor Authentication" %}</a></p>
|
|
34
52
|
{% else %}
|
|
35
|
-
<p>{% blocktrans %}Two-factor authentication is not enabled for your
|
|
53
|
+
<p>{% blocktrans trimmed %}Two-factor authentication is not enabled for your
|
|
36
54
|
account. Enable two-factor authentication for enhanced account
|
|
37
55
|
security.{% endblocktrans %}</p>
|
|
38
|
-
<p><a href="{% url 'two_factor:setup' %}" class="
|
|
56
|
+
<p><a href="{% url 'two_factor:setup' %}" class="btn btn-primary">
|
|
39
57
|
{% trans "Enable Two-Factor Authentication" %}</a>
|
|
40
58
|
</p>
|
|
41
59
|
{% endif %}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from captcha.fields import ReCaptchaField
|
|
2
|
-
from captcha.widgets import ReCaptchaV2Invisible
|
|
3
1
|
from django import forms
|
|
4
2
|
from django.test import TestCase
|
|
3
|
+
from django_recaptcha.fields import ReCaptchaField
|
|
4
|
+
from django_recaptcha.widgets import ReCaptchaV2Invisible
|
|
5
5
|
|
|
6
6
|
from portal.helpers.captcha import is_captcha_in_form, remove_captcha_from_forms
|
|
7
7
|
|
portal/tests/test_middleware.py
CHANGED
|
@@ -32,7 +32,9 @@ class TestAdminAccessMiddleware(TestCase):
|
|
|
32
32
|
self.email, self.password = self._setup_user()
|
|
33
33
|
|
|
34
34
|
self.monkeypatch = MonkeyPatch()
|
|
35
|
-
self.monkeypatch.setattr(
|
|
35
|
+
self.monkeypatch.setattr(
|
|
36
|
+
"deploy.middleware.admin_access.MODULE_NAME", "test"
|
|
37
|
+
)
|
|
36
38
|
|
|
37
39
|
def _setup_user(self) -> Tuple[str, str]:
|
|
38
40
|
email, password = signup_teacher_directly()
|
|
@@ -79,7 +81,11 @@ class TestAdminAccessMiddleware(TestCase):
|
|
|
79
81
|
assert type(response) == HttpResponseRedirect
|
|
80
82
|
assert response.url == "/teach/dashboard/"
|
|
81
83
|
|
|
82
|
-
@mock.patch(
|
|
84
|
+
@mock.patch(
|
|
85
|
+
"deploy.middleware.admin_access.using_two_factor",
|
|
86
|
+
return_value=True,
|
|
87
|
+
autospec=True,
|
|
88
|
+
)
|
|
83
89
|
def test_non_superuser_with_2FA_is_redirected(self, mock_using_two_factor):
|
|
84
90
|
self.client.login(username=self.email, password=self.password)
|
|
85
91
|
|
|
@@ -91,8 +97,14 @@ class TestAdminAccessMiddleware(TestCase):
|
|
|
91
97
|
assert type(response) == HttpResponseRedirect
|
|
92
98
|
assert response.url == "/teach/dashboard/"
|
|
93
99
|
|
|
94
|
-
@mock.patch(
|
|
95
|
-
|
|
100
|
+
@mock.patch(
|
|
101
|
+
"deploy.middleware.admin_access.using_two_factor",
|
|
102
|
+
return_value=True,
|
|
103
|
+
autospec=True,
|
|
104
|
+
)
|
|
105
|
+
def test_superuser_with_2FA_can_access_admin_site(
|
|
106
|
+
self, mock_using_two_factor
|
|
107
|
+
):
|
|
96
108
|
self._make_user_superuser()
|
|
97
109
|
|
|
98
110
|
self.client.login(username=self.email, password=self.password)
|
|
@@ -118,7 +130,7 @@ class TestSecurityMiddleware(TestCase):
|
|
|
118
130
|
assert response.headers["cache-control"] == "private"
|
|
119
131
|
assert response.headers["x-content-type-options"] == "nosniff"
|
|
120
132
|
assert response.headers["x-frame-options"] == "DENY"
|
|
121
|
-
assert response.headers["x-xss-protection"] == "1"
|
|
133
|
+
assert response.headers["x-xss-protection"] == "1; mode=block"
|
|
122
134
|
|
|
123
135
|
|
|
124
136
|
class TestSessionTimeoutMiddleware(TestCase):
|
|
@@ -132,7 +144,9 @@ class TestSessionTimeoutMiddleware(TestCase):
|
|
|
132
144
|
self.email, self.password = self._setup_user()
|
|
133
145
|
|
|
134
146
|
self.monkeypatch = MonkeyPatch()
|
|
135
|
-
self.monkeypatch.setattr(
|
|
147
|
+
self.monkeypatch.setattr(
|
|
148
|
+
"deploy.middleware.session_timeout.SESSION_EXPIRY_TIME", 5
|
|
149
|
+
)
|
|
136
150
|
|
|
137
151
|
def _setup_user(self) -> Tuple[str, str]:
|
|
138
152
|
email, password = signup_teacher_directly()
|
|
@@ -211,14 +225,18 @@ class TestScreentimeWarningMiddleware(TestCase):
|
|
|
211
225
|
self.client.get("/")
|
|
212
226
|
session = self.client.session
|
|
213
227
|
assert "screentime_warning_timeout" in session
|
|
214
|
-
previous_screentime_warning_timeout = session[
|
|
228
|
+
previous_screentime_warning_timeout = session[
|
|
229
|
+
"screentime_warning_timeout"
|
|
230
|
+
]
|
|
215
231
|
|
|
216
232
|
self.client.get("/")
|
|
217
233
|
session = self.client.session
|
|
218
234
|
assert "screentime_warning_timeout" in session
|
|
219
235
|
new_screentime_warning_timeout = session["screentime_warning_timeout"]
|
|
220
236
|
|
|
221
|
-
assert
|
|
237
|
+
assert (
|
|
238
|
+
new_screentime_warning_timeout < previous_screentime_warning_timeout
|
|
239
|
+
)
|
|
222
240
|
|
|
223
241
|
# Check the reset_screentime_warning API resets the timeout
|
|
224
242
|
url = reverse("reset_screentime_warning")
|
|
@@ -226,5 +244,9 @@ class TestScreentimeWarningMiddleware(TestCase):
|
|
|
226
244
|
self.client.get("/")
|
|
227
245
|
session = self.client.session
|
|
228
246
|
assert "screentime_warning_timeout" in session
|
|
229
|
-
renewed_screentime_warning_timeout = session[
|
|
230
|
-
|
|
247
|
+
renewed_screentime_warning_timeout = session[
|
|
248
|
+
"screentime_warning_timeout"
|
|
249
|
+
]
|
|
250
|
+
assert (
|
|
251
|
+
renewed_screentime_warning_timeout > new_screentime_warning_timeout
|
|
252
|
+
)
|