Initial mfa support (for internal users) #93591
28
bid_main/templates/bid_main/components/mfa_form.html
Normal file
28
bid_main/templates/bid_main/components/mfa_form.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{% load add_form_classes from forms %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<div class="bid box">
|
||||||
|
<div>
|
||||||
|
<h2>Multi-factor Authentication</h2>
|
||||||
|
</div>
|
||||||
|
{% with form=form|add_form_classes %}
|
||||||
|
<form role="login" action="" method="POST">{% csrf_token %}
|
||||||
|
<fieldset>
|
||||||
|
{% with field=form.otp_device %}
|
||||||
|
{% include "components/forms/field.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% with field=form.otp_token %}
|
||||||
|
{% include "components/forms/field.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% with field=form.otp_trust_agent %}
|
||||||
|
{% include "components/forms/field.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% if form.errors %}
|
||||||
|
<p class="text-danger">{{ form.errors }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</fieldset>
|
||||||
|
<button class="btn btn-block btn-accent" id="register">Continue</button>
|
||||||
|
</form>
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
@ -139,6 +139,9 @@ Profile
|
|||||||
<a class="btn" href="{% url 'bid_main:active_sessions' %}">
|
<a class="btn" href="{% url 'bid_main:active_sessions' %}">
|
||||||
<span>Active Sessions</span>
|
<span>Active Sessions</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="btn" href="{% url 'bid_main:mfa' %}">
|
||||||
|
<span>Multi-factor Authentication</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-row-fluid mt-3">
|
<div class="btn-row-fluid mt-3">
|
||||||
<a class="btn" href="{% url 'bid_main:password_change' %}">
|
<a class="btn" href="{% url 'bid_main:password_change' %}">
|
||||||
|
@ -3,5 +3,9 @@
|
|||||||
{% block page_title %}Sign in{% endblock %}
|
{% block page_title %}Sign in{% endblock %}
|
||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
|
{% if is_authentication_form %}
|
||||||
{% include 'bid_main/components/login_form.html' %}
|
{% include 'bid_main/components/login_form.html' %}
|
||||||
|
{% elif is_mfa_form %}
|
||||||
|
{% include 'bid_main/components/mfa_form.html' %}
|
||||||
|
{% endif %}
|
||||||
{% endblock form %}
|
{% endblock form %}
|
||||||
|
9
bid_main/templates/bid_main/mfa_setup.html
Normal file
9
bid_main/templates/bid_main/mfa_setup.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% load pipeline static %}
|
||||||
|
{% block page_title %}
|
||||||
|
MFA Setup
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{% for d in devices %}{{d}}{% endfor %}
|
||||||
|
{% endblock %}
|
@ -1,6 +1,7 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse_lazy, path, re_path
|
from django.urls import reverse_lazy, path, re_path
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
|
from otp_agents.decorators import otp_required
|
||||||
|
|
||||||
from . import forms
|
from . import forms
|
||||||
from .views import normal_pages, registration_email, json_api, developer_applications
|
from .views import normal_pages, registration_email, json_api, developer_applications
|
||||||
@ -146,6 +147,15 @@ urlpatterns = [
|
|||||||
normal_pages.TerminateSessionView.as_view(),
|
normal_pages.TerminateSessionView.as_view(),
|
||||||
name='terminate_session',
|
name='terminate_session',
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
'mfa/',
|
||||||
|
otp_required(
|
||||||
|
accept_trusted_agent=True,
|
||||||
|
if_configured=True,
|
||||||
|
view=normal_pages.MfaView.as_view(),
|
||||||
|
),
|
||||||
|
name='mfa',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Only enable this on a dev server:
|
# Only enable this on a dev server:
|
||||||
|
@ -20,13 +20,14 @@ from django.urls import reverse_lazy
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.cache import never_cache
|
from django.views.decorators.cache import never_cache
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.views.decorators.debug import sensitive_post_parameters
|
|
||||||
from django.views.generic import TemplateView, FormView
|
from django.views.generic import TemplateView, FormView
|
||||||
from django.views.generic.base import View
|
from django.views.generic.base import View
|
||||||
from django.views.generic.edit import UpdateView
|
from django.views.generic.edit import UpdateView
|
||||||
|
from django_otp import devices_for_user
|
||||||
|
from otp_agents.forms import OTPTokenForm
|
||||||
import loginas.utils
|
import loginas.utils
|
||||||
import oauth2_provider.models as oauth2_models
|
import oauth2_provider.models as oauth2_models
|
||||||
|
import otp_agents.views
|
||||||
|
|
||||||
from .. import forms, email
|
from .. import forms, email
|
||||||
from . import mixins
|
from . import mixins
|
||||||
@ -72,35 +73,22 @@ class IndexView(LoginRequiredMixin, mixins.PageIdMixin, TemplateView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LoginView(mixins.RedirectToPrivacyAgreeMixin, mixins.PageIdMixin, auth_views.LoginView):
|
class LoginView(mixins.RedirectToPrivacyAgreeMixin, mixins.PageIdMixin, otp_agents.views.LoginView):
|
||||||
"""Shows the login view."""
|
"""Shows the login view."""
|
||||||
|
|
||||||
|
otp_authentication_form = forms.AuthenticationForm
|
||||||
page_id = "login"
|
page_id = "login"
|
||||||
template_name = "bid_main/login.html"
|
redirect_authenticated_user = False
|
||||||
authentication_form = forms.AuthenticationForm
|
|
||||||
redirect_authenticated_user = True
|
|
||||||
success_url_allowed_hosts = settings.NEXT_REDIR_AFTER_LOGIN_ALLOWED_HOSTS
|
success_url_allowed_hosts = settings.NEXT_REDIR_AFTER_LOGIN_ALLOWED_HOSTS
|
||||||
|
template_name = "bid_main/login.html"
|
||||||
|
|
||||||
authorize_url = reverse_lazy("oauth2_provider:authorize")
|
authorize_url = reverse_lazy("oauth2_provider:authorize")
|
||||||
|
|
||||||
@method_decorator(sensitive_post_parameters())
|
|
||||||
@method_decorator(csrf_exempt)
|
|
||||||
@method_decorator(never_cache)
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
"""Don't check CSRF token when already authenticated."""
|
|
||||||
if self.redirect_authenticated_user and self.request.user.is_authenticated:
|
|
||||||
redirect_to = self.get_success_url()
|
|
||||||
if redirect_to == self.request.path:
|
|
||||||
raise ValueError(
|
|
||||||
"Redirection loop for authenticated user detected. Check that "
|
|
||||||
"your LOGIN_REDIRECT_URL doesn't point to a login page."
|
|
||||||
)
|
|
||||||
return HttpResponseRedirect(redirect_to)
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs) -> dict:
|
def get_context_data(self, **kwargs) -> dict:
|
||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
self.find_oauth_flow(ctx)
|
self.find_oauth_flow(ctx)
|
||||||
|
ctx["is_authentication_form"] = isinstance(self.get_form(), forms.AuthenticationForm)
|
||||||
|
ctx["is_mfa_form"] = isinstance(self.get_form(), OTPTokenForm)
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
def find_oauth_flow(self, ctx: dict):
|
def find_oauth_flow(self, ctx: dict):
|
||||||
@ -453,3 +441,12 @@ class TerminateSessionView(LoginRequiredMixin, View):
|
|||||||
user_session.terminate()
|
user_session.terminate()
|
||||||
return redirect('bid_main:active_sessions')
|
return redirect('bid_main:active_sessions')
|
||||||
return HttpResponseNotFound("session not found")
|
return HttpResponseNotFound("session not found")
|
||||||
|
|
||||||
|
|
||||||
|
class MfaView(LoginRequiredMixin, TemplateView):
|
||||||
|
template_name = "bid_main/mfa_setup.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
return {
|
||||||
|
'devices': list(devices_for_user(self.request.user)),
|
||||||
|
}
|
||||||
|
@ -56,6 +56,11 @@ INSTALLED_APPS = [
|
|||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
"django.contrib.sites",
|
"django.contrib.sites",
|
||||||
"django.contrib.flatpages",
|
"django.contrib.flatpages",
|
||||||
|
"django_otp",
|
||||||
|
"django_otp.plugins.otp_totp",
|
||||||
|
"django_otp.plugins.otp_hotp",
|
||||||
|
"django_otp.plugins.otp_static",
|
||||||
|
"django_agent_trust",
|
||||||
"oauth2_provider",
|
"oauth2_provider",
|
||||||
"pipeline",
|
"pipeline",
|
||||||
"sorl.thumbnail",
|
"sorl.thumbnail",
|
||||||
@ -73,6 +78,8 @@ MIDDLEWARE = [
|
|||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django_agent_trust.middleware.AgentMiddleware",
|
||||||
|
"django_otp.middleware.OTPMiddleware",
|
||||||
"bid_main.middleware.user_session_middleware",
|
"bid_main.middleware.user_session_middleware",
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
@ -264,6 +271,10 @@ NEXT_REDIR_AFTER_LOGIN_ALLOWED_HOSTS = {
|
|||||||
"blender.community",
|
"blender.community",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AGENT_COOKIE_SECURE = True
|
||||||
|
AGENT_TRUST_DAYS = 30
|
||||||
|
AGENT_INACTIVITY_DAYS = 7
|
||||||
|
|
||||||
CSRF_COOKIE_SECURE = True
|
CSRF_COOKIE_SECURE = True
|
||||||
CSRF_FAILURE_VIEW = "bid_main.views.errors.csrf_failure"
|
CSRF_FAILURE_VIEW = "bid_main.views.errors.csrf_failure"
|
||||||
CSRF_TRUSTED_ORIGINS = ['https://*.blender.org']
|
CSRF_TRUSTED_ORIGINS = ['https://*.blender.org']
|
||||||
@ -349,6 +360,7 @@ if TESTING:
|
|||||||
# For Debug Toolbar, extend with whatever address you use to connect
|
# For Debug Toolbar, extend with whatever address you use to connect
|
||||||
# to your dev server.
|
# to your dev server.
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
|
AGENT_COOKIE_SECURE = False
|
||||||
CSRF_COOKIE_SECURE = False
|
CSRF_COOKIE_SECURE = False
|
||||||
INSTALLED_APPS += ['debug_toolbar']
|
INSTALLED_APPS += ['debug_toolbar']
|
||||||
INTERNAL_IPS = ["127.0.0.1"]
|
INTERNAL_IPS = ["127.0.0.1"]
|
||||||
|
@ -8,15 +8,18 @@ colorama==0.4.6 ; python_version >= "3.8" and python_version < "4" and platform_
|
|||||||
cryptography==41.0.0 ; python_version >= "3.8" and python_version < "4"
|
cryptography==41.0.0 ; python_version >= "3.8" and python_version < "4"
|
||||||
csscompressor==0.9.5 ; python_version >= "3.8" and python_version < "4"
|
csscompressor==0.9.5 ; python_version >= "3.8" and python_version < "4"
|
||||||
deprecated==1.2.14 ; python_version >= "3.8" and python_version < "4"
|
deprecated==1.2.14 ; python_version >= "3.8" and python_version < "4"
|
||||||
dj-database-url==2.2.0
|
django==4.2.13 ; python_version >= "3.8" and python_version < "4"
|
||||||
django-admin-select2==1.0.1 ; python_version >= "3.8" and python_version < "4"
|
django-admin-select2==1.0.1 ; python_version >= "3.8" and python_version < "4"
|
||||||
|
django-agent-trust==1.1.0
|
||||||
django-background-tasks-updated @ git+https://projects.blender.org/infrastructure/django-background-tasks.git@1.2.10
|
django-background-tasks-updated @ git+https://projects.blender.org/infrastructure/django-background-tasks.git@1.2.10
|
||||||
|
django[bcrypt]==4.2.13 ; python_version >= "3.8" and python_version < "4"
|
||||||
django-compat==1.0.15 ; python_version >= "3.8" and python_version < "4"
|
django-compat==1.0.15 ; python_version >= "3.8" and python_version < "4"
|
||||||
django-loginas==0.3.11 ; python_version >= "3.8" and python_version < "4"
|
django-loginas==0.3.11 ; python_version >= "3.8" and python_version < "4"
|
||||||
django-oauth-toolkit @ git+https://projects.blender.org/Oleg-Komarov/django-oauth-toolkit.git@0b056a99ca943771615b859f48aaff0e12357f22 ; python_version >= "3.8" and python_version < "4"
|
django-oauth-toolkit @ git+https://projects.blender.org/Oleg-Komarov/django-oauth-toolkit.git@0b056a99ca943771615b859f48aaff0e12357f22 ; python_version >= "3.8" and python_version < "4"
|
||||||
|
django-otp==1.5.1
|
||||||
|
django-otp-agents==1.0.1
|
||||||
django-pipeline==3.1.0 ; python_version >= "3.8" and python_version < "4"
|
django-pipeline==3.1.0 ; python_version >= "3.8" and python_version < "4"
|
||||||
django==4.2.13 ; python_version >= "3.8" and python_version < "4"
|
dj-database-url==2.2.0
|
||||||
django[bcrypt]==4.2.13 ; python_version >= "3.8" and python_version < "4"
|
|
||||||
docutils==0.14 ; python_version >= "3.8" and python_version < "4"
|
docutils==0.14 ; python_version >= "3.8" and python_version < "4"
|
||||||
htmlmin==0.1.12 ; python_version >= "3.8" and python_version < "4"
|
htmlmin==0.1.12 ; python_version >= "3.8" and python_version < "4"
|
||||||
idna==2.8 ; python_version >= "3.8" and python_version < "4"
|
idna==2.8 ; python_version >= "3.8" and python_version < "4"
|
||||||
|
Loading…
Reference in New Issue
Block a user