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' %}">
|
||||
<span>Active Sessions</span>
|
||||
</a>
|
||||
<a class="btn" href="{% url 'bid_main:mfa' %}">
|
||||
<span>Multi-factor Authentication</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="btn-row-fluid mt-3">
|
||||
<a class="btn" href="{% url 'bid_main:password_change' %}">
|
||||
|
@ -3,5 +3,9 @@
|
||||
{% block page_title %}Sign in{% endblock %}
|
||||
|
||||
{% block form %}
|
||||
{% if is_authentication_form %}
|
||||
{% include 'bid_main/components/login_form.html' %}
|
||||
{% elif is_mfa_form %}
|
||||
{% include 'bid_main/components/mfa_form.html' %}
|
||||
{% endif %}
|
||||
{% 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.urls import reverse_lazy, path, re_path
|
||||
from django.contrib.auth import views as auth_views
|
||||
from otp_agents.decorators import otp_required
|
||||
|
||||
from . import forms
|
||||
from .views import normal_pages, registration_email, json_api, developer_applications
|
||||
@ -146,6 +147,15 @@ urlpatterns = [
|
||||
normal_pages.TerminateSessionView.as_view(),
|
||||
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:
|
||||
|
@ -20,13 +20,14 @@ from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
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.base import View
|
||||
from django.views.generic.edit import UpdateView
|
||||
from django_otp import devices_for_user
|
||||
from otp_agents.forms import OTPTokenForm
|
||||
import loginas.utils
|
||||
import oauth2_provider.models as oauth2_models
|
||||
import otp_agents.views
|
||||
|
||||
from .. import forms, email
|
||||
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."""
|
||||
|
||||
otp_authentication_form = forms.AuthenticationForm
|
||||
page_id = "login"
|
||||
template_name = "bid_main/login.html"
|
||||
authentication_form = forms.AuthenticationForm
|
||||
redirect_authenticated_user = True
|
||||
redirect_authenticated_user = False
|
||||
success_url_allowed_hosts = settings.NEXT_REDIR_AFTER_LOGIN_ALLOWED_HOSTS
|
||||
template_name = "bid_main/login.html"
|
||||
|
||||
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:
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
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
|
||||
|
||||
def find_oauth_flow(self, ctx: dict):
|
||||
@ -453,3 +441,12 @@ class TerminateSessionView(LoginRequiredMixin, View):
|
||||
user_session.terminate()
|
||||
return redirect('bid_main:active_sessions')
|
||||
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.sites",
|
||||
"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",
|
||||
"pipeline",
|
||||
"sorl.thumbnail",
|
||||
@ -73,6 +78,8 @@ MIDDLEWARE = [
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django_agent_trust.middleware.AgentMiddleware",
|
||||
"django_otp.middleware.OTPMiddleware",
|
||||
"bid_main.middleware.user_session_middleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
@ -264,6 +271,10 @@ NEXT_REDIR_AFTER_LOGIN_ALLOWED_HOSTS = {
|
||||
"blender.community",
|
||||
}
|
||||
|
||||
AGENT_COOKIE_SECURE = True
|
||||
AGENT_TRUST_DAYS = 30
|
||||
AGENT_INACTIVITY_DAYS = 7
|
||||
|
||||
CSRF_COOKIE_SECURE = True
|
||||
CSRF_FAILURE_VIEW = "bid_main.views.errors.csrf_failure"
|
||||
CSRF_TRUSTED_ORIGINS = ['https://*.blender.org']
|
||||
@ -349,6 +360,7 @@ if TESTING:
|
||||
# For Debug Toolbar, extend with whatever address you use to connect
|
||||
# to your dev server.
|
||||
if DEBUG:
|
||||
AGENT_COOKIE_SECURE = False
|
||||
CSRF_COOKIE_SECURE = False
|
||||
INSTALLED_APPS += ['debug_toolbar']
|
||||
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"
|
||||
csscompressor==0.9.5 ; 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-agent-trust==1.1.0
|
||||
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-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-otp==1.5.1
|
||||
django-otp-agents==1.0.1
|
||||
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"
|
||||
django[bcrypt]==4.2.13 ; python_version >= "3.8" and python_version < "4"
|
||||
dj-database-url==2.2.0
|
||||
docutils==0.14 ; 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"
|
||||
|
Loading…
Reference in New Issue
Block a user