Initial mfa support (for internal users) #93591

Merged
Oleg-Komarov merged 46 commits from mfa into main 2024-08-29 11:44:06 +02:00
10 changed files with 47 additions and 54 deletions
Showing only changes of commit 631e30d6b7 - Show all commits

View File

@ -1,7 +1,7 @@
"""Give everybody with a NULL nickname something based on their full name.""" """Give everybody with a NULL nickname something based on their full name."""
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, IntegrityError, transaction from django.db import migrations
def set_nickname(model, user, nickname) -> bool: def set_nickname(model, user, nickname) -> bool:
@ -9,7 +9,6 @@ def set_nickname(model, user, nickname) -> bool:
count = model.objects.filter(nickname=nickname).count() count = model.objects.filter(nickname=nickname).count()
if count > 0: if count > 0:
print(f'already {count} user(s) with nickname {nickname!r}')
return False return False
user.nickname = nickname user.nickname = nickname
@ -30,7 +29,6 @@ def random_nums():
def fill_nicknames(apps, schema_editor): def fill_nicknames(apps, schema_editor):
"""Give users with a NULL nickname something based on their full name.""" """Give users with a NULL nickname something based on their full name."""
import re import re
import datetime
# We can't import the User model directly as it may be a newer # We can't import the User model directly as it may be a newer
# version than this migration expects. We use the historical version. # version than this migration expects. We use the historical version.
@ -42,14 +40,10 @@ def fill_nicknames(apps, schema_editor):
users = User.objects.filter(nickname=None) users = User.objects.filter(nickname=None)
total_users = users.count() total_users = users.count()
last_report = 0 last_report = 0
start = datetime.datetime.now()
print()
print(f' - migrating {total_users} users.')
for idx, user in enumerate(users): for idx, user in enumerate(users):
perc = idx / total_users perc = idx / total_users
if perc - last_report > 0.10: if perc - last_report > 0.10:
last_report = perc last_report = perc
print(f' - {idx} ({int(perc*100)}%)')
# We cannot migrate the entire database in one transaction (too many # We cannot migrate the entire database in one transaction (too many
# queries for MySQL to handle), so do one transaction per user. # queries for MySQL to handle), so do one transaction per user.
@ -63,9 +57,6 @@ def fill_nicknames(apps, schema_editor):
if set_nickname(User, user, f'{base}-{num}'): if set_nickname(User, user, f'{base}-{num}'):
break break
end = datetime.datetime.now() # assume the timezone hasn't changed.
print(f'Migration of {total_users} took {end - start}')
def fake_reverse(apps, schema_editor): def fake_reverse(apps, schema_editor):
"""Allow reversal of this migration really reversing.""" """Allow reversal of this migration really reversing."""

View File

@ -3,18 +3,18 @@
{% block confirm_email_body %} {% block confirm_email_body %}
<h2>Confirm your email address</h2> <h2>Confirm your email address</h2>
<p> <p>
We have sent an email with instructions to <strong>{{ user.email_to_confirm }}</strong>. We have sent an email with instructions to <strong>{{ user.email_to_confirm }}</strong>.
</p> </p>
<p> <p>
If the email doesn't arrive shortly, please check your spam folder. If the email doesn't arrive shortly, please check your spam folder.
</p> </p>
<div id="poll_ok" style="display: non"> <div id="poll_ok" style="display: none">
<div class="alert alert-success"> <div class="alert alert-success">
Your email address has been confirmed. Your email address has been confirmed.
</div> </div>
<div class="btn-row justify-content-center mt-3"> <div class="btn-row justify-content-center mt-3">
<a class="btn btn-primary px-5" href="{% url 'bid_main:index' %}">Done</a> <a class="btn btn-primary px-5" href="{% url 'bid_main:index' %}">Done</a>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
@ -24,26 +24,26 @@
var poll_url = '{% url 'bid_main:confirm-email-poll' %}'; var poll_url = '{% url 'bid_main:confirm-email-poll' %}';
var $result = $('#poll_result'); var $result = $('#poll_result');
function poll() { function poll(attemptCount) {
$.get(poll_url) if (attemptCount > 5) {
.done(function(data) { // Stop, don't poll forever
if (typeof(data.confirmed) === 'undefined' || data.confirmed == null) { return;
// Not yet confirmed, wait longer }
window.setTimeout(poll, 2500); $.get(poll_url)
return; .done(function(data) {
} if (typeof(data.confirmed) === 'undefined' || data.confirmed == null) {
// Confirmation has been confirmed! // Not yet confirmed, wait longer with exponential backoff
$('#poll_ok').show(); attemptCount += 1;
}) window.setTimeout(poll, 10000 * (2 ** attemptCount), attemptCount);
.fail(function(err) { return;
// This is a frequent poll and the email address confirmation system works }
// fine without it, so let's not bother the user with error messages. // Confirmation has been confirmed!
if (console) console.log('Error: ', err); $('#poll_ok').show();
})
// Try again after a while. .fail(function(err) {
window.setTimeout(poll, 4000); // Stop on error
}); if (console) console.log('Error: ', err);
});
} }
// Do a quick test first, then slow down a little bit. window.setTimeout(poll, 1000, 0);
window.setTimeout(poll, 250);
</script>{% endblock footer_scripts %} </script>{% endblock footer_scripts %}

View File

@ -146,11 +146,9 @@ TEMPLATES = [
WSGI_APPLICATION = "blenderid.wsgi.application" WSGI_APPLICATION = "blenderid.wsgi.application"
DATABASES = { DATABASES = {
'default': dj_database_url.config( 'default': dj_database_url.config(default='sqlite:///{}'.format(BASE_DIR / 'db.sqlite3')),
default='postgresql://blender_id:blender_id@127.0.0.1:5432/blender_id',
conn_max_age=600,
),
} }
DATABASES['default']['CONN_MAX_AGE'] = 600
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

View File

@ -45,7 +45,7 @@ To encrypt this value, use the following command:
echo -n 'https://foo@bar.example.com/1234' | ansible-vault encrypt_string --vault-id production@prompt --stdin-name 'sentry_dsn' echo -n 'https://foo@bar.example.com/1234' | ansible-vault encrypt_string --vault-id production@prompt --stdin-name 'sentry_dsn'
Store the ouput of the above command in `environments/production/group_vars/all/99_vault.yaml` Store the output of the above command in `environments/production/group_vars/all/99_vault.yaml`
(not tracked by this repository): (not tracked by this repository):
``` ```

View File

@ -1,8 +1,10 @@
env: staging env: staging
host: web-2.internal host: web-staging-1.hz-nbg1.blender.internal
db_host: db-postgres-2.internal db_host: db-postgres-staging-1.hz-nbg1.blender.internal
db_name: id_staging db_name: id_staging
db_user: id_staging db_user: id_staging
branch: main branch: main
ssl_only: true ssl_only: true
backup_dir: /data/backups

View File

@ -1,8 +1,8 @@
--- ---
ingress: ingress:
hosts: hosts:
lb-2: lb-staging-1.hz-nbg1.blender.internal:
application: application:
hosts: hosts:
web-2: web-staging-1.hz-nbg1.blender.internal:

@ -1 +1 @@
Subproject commit f3e705b712980c5d2bce58dd94b51665858a2dc3 Subproject commit 2e9a85dc8b2f9bd3cb807de5a489615355b50f70

View File

@ -36,15 +36,16 @@ rate_limit:
burst: 50 burst: 50
delay: 10 delay: 10
mailto: cron@blender.org
certbot: certbot:
email: root@blender.org email: root@blender.org
source_url: https://projects.blender.org/infrastructure/{{ project_slug }}.git source_url: https://projects.blender.org/infrastructure/{{ project_slug }}.git
branch: production branch: production
backup_dir: /mnt/backup
ssl_only: false ssl_only: false
PGSSLROOTCERT: /usr/local/share/ca-certificates/cloud-init-ca-cert-1.crt ca_certificate: /usr/local/share/ca-certificates/cloud-init-ca-cert-1.crt
include_common_services: include_common_services:
- background - background
@ -52,4 +53,5 @@ include_common_services:
- backup-service-dirs - backup-service-dirs
- clearsessions - clearsessions
- delete-completed-tasks - delete-completed-tasks
- notify-email@
- process-deletion-requests - process-deletion-requests

View File

@ -37,7 +37,6 @@ more-itertools==7.2.0 ; python_version >= "3.8" and python_version < "4"
oauthlib==3.1.0 ; python_version >= "3.8" and python_version < "4" oauthlib==3.1.0 ; python_version >= "3.8" and python_version < "4"
pep562==1.0 ; python_version >= "3.8" and python_version < "4" pep562==1.0 ; python_version >= "3.8" and python_version < "4"
pillow==9.1.0 ; python_version >= "3.8" and python_version < "4" pillow==9.1.0 ; python_version >= "3.8" and python_version < "4"
psycopg2==2.8.6 ; python_version >= "3.8" and python_version < "4"
pycparser==2.19 ; python_version >= "3.8" and python_version < "4" pycparser==2.19 ; python_version >= "3.8" and python_version < "4"
pygments==2.17.2 ; python_version >= "3.8" and python_version < "4" pygments==2.17.2 ; python_version >= "3.8" and python_version < "4"
pyinstrument==4.6.0 ; python_version >= "3.8" and python_version < "4" pyinstrument==4.6.0 ; python_version >= "3.8" and python_version < "4"
@ -58,6 +57,5 @@ sqlparse==0.5.0 ; python_version >= "3.8" and python_version < "4"
tornado==6.0.3 ; python_version >= "3.8" and python_version < "4" tornado==6.0.3 ; python_version >= "3.8" and python_version < "4"
urllib3==1.25.11 ; python_version >= "3.8" and python_version < "4" urllib3==1.25.11 ; python_version >= "3.8" and python_version < "4"
user-agents==2.2.0 user-agents==2.2.0
uwsgi==2.0.23
wrapt==1.15.0 ; python_version >= "3.8" and python_version < "4" wrapt==1.15.0 ; python_version >= "3.8" and python_version < "4"
zipp==0.6.0 ; python_version >= "3.8" and python_version < "4" zipp==0.6.0 ; python_version >= "3.8" and python_version < "4"

View File

@ -1 +1,3 @@
-r requirements.txt -r requirements.txt
psycopg2==2.8.6
uwsgi==2.0.23